@shirudo/ddd-kit 2.0.0 → 2.2.0
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/README.md +6 -3
- package/dist/{aggregate-CePTINEt.d.ts → aggregate-DFi6HlEh.d.ts} +113 -64
- package/dist/index.d.ts +876 -196
- package/dist/index.js +2027 -396
- package/dist/index.js.map +1 -1
- package/dist/presentation.js +1 -1
- package/dist/presentation.js.map +1 -1
- package/dist/testing.d.ts +124 -2
- package/dist/testing.js +535 -116
- package/dist/testing.js.map +1 -1
- package/dist/utils.d.ts +2 -1
- package/dist/utils.js +160 -18
- package/dist/utils.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -17,6 +17,7 @@ var BUILT_IN_TAGS = /* @__PURE__ */ new Set([
|
|
|
17
17
|
"[object Boolean]",
|
|
18
18
|
"[object Number]",
|
|
19
19
|
"[object String]",
|
|
20
|
+
"[object BigInt]",
|
|
20
21
|
"[object ArrayBuffer]",
|
|
21
22
|
"[object SharedArrayBuffer]",
|
|
22
23
|
"[object DataView]"
|
|
@@ -37,11 +38,117 @@ var arrayBufferByteLengthGet = intrinsicGetter(
|
|
|
37
38
|
ArrayBuffer.prototype,
|
|
38
39
|
"byteLength"
|
|
39
40
|
);
|
|
41
|
+
var sharedArrayBufferByteLengthGet = typeof SharedArrayBuffer === "undefined" ? void 0 : intrinsicGetter(SharedArrayBuffer.prototype, "byteLength");
|
|
40
42
|
var regExpSourceGet = intrinsicGetter(RegExp.prototype, "source");
|
|
41
43
|
var booleanValueOf = Boolean.prototype.valueOf;
|
|
42
44
|
var numberValueOf = Number.prototype.valueOf;
|
|
43
45
|
var stringValueOf = String.prototype.valueOf;
|
|
46
|
+
var bigIntValueOf = BigInt.prototype.valueOf;
|
|
47
|
+
var functionToString = Function.prototype.toString;
|
|
44
48
|
var PROBE_KEY = {};
|
|
49
|
+
var INTRINSIC_CONSTRUCTOR_NAMES = [
|
|
50
|
+
"Object",
|
|
51
|
+
"Array",
|
|
52
|
+
"Date",
|
|
53
|
+
"RegExp",
|
|
54
|
+
"Map",
|
|
55
|
+
"Set",
|
|
56
|
+
"WeakMap",
|
|
57
|
+
"WeakSet",
|
|
58
|
+
"Promise",
|
|
59
|
+
"Error",
|
|
60
|
+
"EvalError",
|
|
61
|
+
"RangeError",
|
|
62
|
+
"ReferenceError",
|
|
63
|
+
"SyntaxError",
|
|
64
|
+
"TypeError",
|
|
65
|
+
"URIError",
|
|
66
|
+
"AggregateError",
|
|
67
|
+
"Boolean",
|
|
68
|
+
"Number",
|
|
69
|
+
"String",
|
|
70
|
+
"BigInt",
|
|
71
|
+
"ArrayBuffer",
|
|
72
|
+
"SharedArrayBuffer",
|
|
73
|
+
"DataView",
|
|
74
|
+
"Int8Array",
|
|
75
|
+
"Uint8Array",
|
|
76
|
+
"Uint8ClampedArray",
|
|
77
|
+
"Int16Array",
|
|
78
|
+
"Uint16Array",
|
|
79
|
+
"Int32Array",
|
|
80
|
+
"Uint32Array",
|
|
81
|
+
"Float32Array",
|
|
82
|
+
"Float64Array",
|
|
83
|
+
"BigInt64Array",
|
|
84
|
+
"BigUint64Array"
|
|
85
|
+
];
|
|
86
|
+
var intrinsicConstructorSources = new Map(
|
|
87
|
+
INTRINSIC_CONSTRUCTOR_NAMES.flatMap((name) => {
|
|
88
|
+
const descriptor = Object.getOwnPropertyDescriptor(globalThis, name);
|
|
89
|
+
const intrinsic = descriptor?.value;
|
|
90
|
+
return typeof intrinsic === "function" ? [[name, functionToString.call(intrinsic)]] : [];
|
|
91
|
+
})
|
|
92
|
+
);
|
|
93
|
+
var intrinsicConstructorSourceSet = new Set(
|
|
94
|
+
intrinsicConstructorSources.values()
|
|
95
|
+
);
|
|
96
|
+
var ERROR_INTRINSIC_NAMES = [
|
|
97
|
+
"Error",
|
|
98
|
+
"EvalError",
|
|
99
|
+
"RangeError",
|
|
100
|
+
"ReferenceError",
|
|
101
|
+
"SyntaxError",
|
|
102
|
+
"TypeError",
|
|
103
|
+
"URIError",
|
|
104
|
+
"AggregateError"
|
|
105
|
+
];
|
|
106
|
+
function isIntrinsicConstructorPrototype(prototype, expectedName) {
|
|
107
|
+
const constructorDescriptor = Object.getOwnPropertyDescriptor(
|
|
108
|
+
prototype,
|
|
109
|
+
"constructor"
|
|
110
|
+
);
|
|
111
|
+
const candidateConstructor = constructorDescriptor?.value;
|
|
112
|
+
if (constructorDescriptor === void 0 || !("value" in constructorDescriptor) || typeof candidateConstructor !== "function") {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
let candidateSource;
|
|
116
|
+
try {
|
|
117
|
+
candidateSource = functionToString.call(candidateConstructor);
|
|
118
|
+
} catch {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
const expectedSource = expectedName === void 0 ? void 0 : intrinsicConstructorSources.get(expectedName);
|
|
122
|
+
if (expectedSource !== void 0 && candidateSource !== expectedSource || expectedSource === void 0 && !intrinsicConstructorSourceSet.has(candidateSource)) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
const nameDescriptor = Object.getOwnPropertyDescriptor(
|
|
126
|
+
candidateConstructor,
|
|
127
|
+
"name"
|
|
128
|
+
);
|
|
129
|
+
const candidateName = nameDescriptor !== void 0 && "value" in nameDescriptor && typeof nameDescriptor.value === "string" ? nameDescriptor.value : void 0;
|
|
130
|
+
const intrinsicName = expectedName ?? candidateName;
|
|
131
|
+
const intrinsicSource = intrinsicName === void 0 ? void 0 : intrinsicConstructorSources.get(intrinsicName);
|
|
132
|
+
return candidateName === intrinsicName && intrinsicSource !== void 0 && candidateSource === intrinsicSource && Object.getOwnPropertyDescriptor(candidateConstructor, "prototype")?.value === prototype;
|
|
133
|
+
}
|
|
134
|
+
__name(isIntrinsicConstructorPrototype, "isIntrinsicConstructorPrototype");
|
|
135
|
+
function hasIntrinsicPrototypeChain(value, expectedName) {
|
|
136
|
+
const visited = /* @__PURE__ */ new WeakSet();
|
|
137
|
+
let prototype = Object.getPrototypeOf(value);
|
|
138
|
+
while (prototype !== null && !visited.has(prototype)) {
|
|
139
|
+
visited.add(prototype);
|
|
140
|
+
if (Object.hasOwn(prototype, "constructor")) {
|
|
141
|
+
return isIntrinsicConstructorPrototype(prototype, expectedName);
|
|
142
|
+
}
|
|
143
|
+
const ownKeys2 = Reflect.ownKeys(prototype);
|
|
144
|
+
if (ownKeys2.length !== 1 || ownKeys2[0] !== Symbol.toStringTag) {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
prototype = Object.getPrototypeOf(prototype);
|
|
148
|
+
}
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
__name(hasIntrinsicPrototypeChain, "hasIntrinsicPrototypeChain");
|
|
45
152
|
var REFERENCE_COMPARED_TAGS = /* @__PURE__ */ new Set([
|
|
46
153
|
"[object Error]",
|
|
47
154
|
"[object ArrayBuffer]",
|
|
@@ -50,6 +157,72 @@ var REFERENCE_COMPARED_TAGS = /* @__PURE__ */ new Set([
|
|
|
50
157
|
"[object WeakMap]",
|
|
51
158
|
"[object WeakSet]"
|
|
52
159
|
]);
|
|
160
|
+
function findPropertyDescriptor(value, key) {
|
|
161
|
+
const visited = /* @__PURE__ */ new WeakSet();
|
|
162
|
+
let current = value;
|
|
163
|
+
while (current !== null && !visited.has(current)) {
|
|
164
|
+
visited.add(current);
|
|
165
|
+
const descriptor = Object.getOwnPropertyDescriptor(current, key);
|
|
166
|
+
if (descriptor !== void 0) return descriptor;
|
|
167
|
+
current = Object.getPrototypeOf(current);
|
|
168
|
+
}
|
|
169
|
+
return void 0;
|
|
170
|
+
}
|
|
171
|
+
__name(findPropertyDescriptor, "findPropertyDescriptor");
|
|
172
|
+
function builtInTagWithoutInvokingAccessors(value) {
|
|
173
|
+
if (ArrayBuffer.isView(value)) {
|
|
174
|
+
return hasBrand(value, "[object DataView]") ? "[object DataView]" : "[object TypedArray]";
|
|
175
|
+
}
|
|
176
|
+
const descriptor = findPropertyDescriptor(value, Symbol.toStringTag);
|
|
177
|
+
if (descriptor !== void 0 && !("value" in descriptor)) {
|
|
178
|
+
return builtInTagFromBrand(value);
|
|
179
|
+
}
|
|
180
|
+
const tag = Object.prototype.toString.call(value);
|
|
181
|
+
if (BUILT_IN_TAGS.has(tag) && hasBrand(value, tag)) {
|
|
182
|
+
return tag;
|
|
183
|
+
}
|
|
184
|
+
return descriptor === void 0 ? void 0 : builtInTagFromBrand(value);
|
|
185
|
+
}
|
|
186
|
+
__name(builtInTagWithoutInvokingAccessors, "builtInTagWithoutInvokingAccessors");
|
|
187
|
+
function mutableBuiltInTagWithoutInvokingAccessors(value) {
|
|
188
|
+
const tag = builtInTagWithoutInvokingAccessors(value);
|
|
189
|
+
return tag === "[object Date]" || tag === "[object Map]" || tag === "[object Set]" ? tag : void 0;
|
|
190
|
+
}
|
|
191
|
+
__name(mutableBuiltInTagWithoutInvokingAccessors, "mutableBuiltInTagWithoutInvokingAccessors");
|
|
192
|
+
function builtInTagFromBrand(value) {
|
|
193
|
+
if (hasBrand(value, "[object Date]")) return "[object Date]";
|
|
194
|
+
if (hasBrand(value, "[object RegExp]")) return "[object RegExp]";
|
|
195
|
+
if (hasBrand(value, "[object Map]")) return "[object Map]";
|
|
196
|
+
if (hasBrand(value, "[object Set]")) return "[object Set]";
|
|
197
|
+
if (hasBrand(value, "[object WeakMap]")) return "[object WeakMap]";
|
|
198
|
+
if (hasBrand(value, "[object WeakSet]")) return "[object WeakSet]";
|
|
199
|
+
if (hasBrand(value, "[object DataView]")) return "[object DataView]";
|
|
200
|
+
if (hasBrand(value, "[object ArrayBuffer]")) return "[object ArrayBuffer]";
|
|
201
|
+
if (hasBrand(value, "[object SharedArrayBuffer]")) {
|
|
202
|
+
return "[object SharedArrayBuffer]";
|
|
203
|
+
}
|
|
204
|
+
if (hasBrand(value, "[object Boolean]")) return "[object Boolean]";
|
|
205
|
+
if (hasBrand(value, "[object Number]")) return "[object Number]";
|
|
206
|
+
if (hasBrand(value, "[object String]")) return "[object String]";
|
|
207
|
+
if (hasBrand(value, "[object BigInt]")) return "[object BigInt]";
|
|
208
|
+
if (hasNativePrototype(value, "Promise")) return "[object Promise]";
|
|
209
|
+
if (hasNativePrototype(value, "Error")) return "[object Error]";
|
|
210
|
+
return void 0;
|
|
211
|
+
}
|
|
212
|
+
__name(builtInTagFromBrand, "builtInTagFromBrand");
|
|
213
|
+
function hasNativePrototype(value, expectedName) {
|
|
214
|
+
const visited = /* @__PURE__ */ new WeakSet();
|
|
215
|
+
let prototype = Object.getPrototypeOf(value);
|
|
216
|
+
while (prototype !== null && !visited.has(prototype)) {
|
|
217
|
+
visited.add(prototype);
|
|
218
|
+
if (isIntrinsicConstructorPrototype(prototype, expectedName)) {
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
prototype = Object.getPrototypeOf(prototype);
|
|
222
|
+
}
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
__name(hasNativePrototype, "hasNativePrototype");
|
|
53
226
|
function hasBrand(obj, tag) {
|
|
54
227
|
try {
|
|
55
228
|
switch (tag) {
|
|
@@ -77,6 +250,10 @@ function hasBrand(obj, tag) {
|
|
|
77
250
|
case "[object ArrayBuffer]":
|
|
78
251
|
arrayBufferByteLengthGet.call(obj);
|
|
79
252
|
return true;
|
|
253
|
+
case "[object SharedArrayBuffer]":
|
|
254
|
+
if (!sharedArrayBufferByteLengthGet) return false;
|
|
255
|
+
sharedArrayBufferByteLengthGet.call(obj);
|
|
256
|
+
return true;
|
|
80
257
|
case "[object Boolean]":
|
|
81
258
|
booleanValueOf.call(obj);
|
|
82
259
|
return true;
|
|
@@ -86,8 +263,17 @@ function hasBrand(obj, tag) {
|
|
|
86
263
|
case "[object String]":
|
|
87
264
|
stringValueOf.call(obj);
|
|
88
265
|
return true;
|
|
89
|
-
|
|
266
|
+
case "[object BigInt]":
|
|
267
|
+
bigIntValueOf.call(obj);
|
|
90
268
|
return true;
|
|
269
|
+
case "[object Promise]":
|
|
270
|
+
return hasNativePrototype(obj, "Promise");
|
|
271
|
+
case "[object Error]":
|
|
272
|
+
return ERROR_INTRINSIC_NAMES.some(
|
|
273
|
+
(name) => hasNativePrototype(obj, name)
|
|
274
|
+
);
|
|
275
|
+
default:
|
|
276
|
+
return false;
|
|
91
277
|
}
|
|
92
278
|
} catch {
|
|
93
279
|
return false;
|
|
@@ -160,12 +346,17 @@ function deepEqualInner(a, b, visited) {
|
|
|
160
346
|
}
|
|
161
347
|
if (Array.isArray(objA) || Array.isArray(objB)) {
|
|
162
348
|
if (!Array.isArray(objA) || !Array.isArray(objB)) return false;
|
|
349
|
+
if (objA.length !== objB.length) return false;
|
|
350
|
+
const keysA = Reflect.ownKeys(objA).filter((key) => key !== "length");
|
|
351
|
+
const keysB = Reflect.ownKeys(objB).filter((key) => key !== "length");
|
|
352
|
+
if (keysA.length !== keysB.length) return false;
|
|
163
353
|
const arrA = objA;
|
|
164
354
|
const arrB = objB;
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
355
|
+
for (const key of keysA) {
|
|
356
|
+
if (!objHasOwn.call(objB, key)) return false;
|
|
357
|
+
if (!deepEqualInner(arrA[key], arrB[key], visited)) {
|
|
358
|
+
return false;
|
|
359
|
+
}
|
|
169
360
|
}
|
|
170
361
|
return true;
|
|
171
362
|
}
|
|
@@ -209,7 +400,8 @@ function deepEqualInner(a, b, visited) {
|
|
|
209
400
|
}
|
|
210
401
|
case "[object Boolean]":
|
|
211
402
|
case "[object Number]":
|
|
212
|
-
case "[object String]":
|
|
403
|
+
case "[object String]":
|
|
404
|
+
case "[object BigInt]": {
|
|
213
405
|
return sameValueZero(
|
|
214
406
|
objA.valueOf(),
|
|
215
407
|
objB.valueOf()
|
|
@@ -224,8 +416,8 @@ __name(deepEqualInner, "deepEqualInner");
|
|
|
224
416
|
function comparePlainObjects(objA, objB, visited) {
|
|
225
417
|
const recA = objA;
|
|
226
418
|
const recB = objB;
|
|
227
|
-
const stringKeysA = Object.
|
|
228
|
-
const stringKeysB = Object.
|
|
419
|
+
const stringKeysA = Object.getOwnPropertyNames(objA);
|
|
420
|
+
const stringKeysB = Object.getOwnPropertyNames(objB);
|
|
229
421
|
if (stringKeysA.length !== stringKeysB.length) return false;
|
|
230
422
|
const symbolKeysA = Object.getOwnPropertySymbols(objA);
|
|
231
423
|
const symbolKeysB = Object.getOwnPropertySymbols(objB);
|
|
@@ -275,19 +467,33 @@ function omitInternal(value, options, ignoreKeys, path, visited, budget) {
|
|
|
275
467
|
if (Array.isArray(obj)) {
|
|
276
468
|
const arr = obj;
|
|
277
469
|
const clone2 = new Array(arr.length);
|
|
470
|
+
const lengthDescriptor = Object.getOwnPropertyDescriptor(arr, "length");
|
|
278
471
|
visited.set(obj, clone2);
|
|
279
|
-
for (
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
)
|
|
472
|
+
for (const key of Reflect.ownKeys(arr)) {
|
|
473
|
+
if (key === "length") continue;
|
|
474
|
+
const segment = arrayPathSegment(key);
|
|
475
|
+
if (typeof segment !== "number" && shouldIgnoreKey(key, path, ignoreKeys, options)) {
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
const descriptor = Object.getOwnPropertyDescriptor(arr, key);
|
|
479
|
+
if (descriptor === void 0) continue;
|
|
480
|
+
path.push(segment);
|
|
481
|
+
if ("value" in descriptor) {
|
|
482
|
+
descriptor.value = omitInternal(
|
|
483
|
+
descriptor.value,
|
|
484
|
+
options,
|
|
485
|
+
ignoreKeys,
|
|
486
|
+
path,
|
|
487
|
+
visited,
|
|
488
|
+
budget
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
Object.defineProperty(clone2, key, descriptor);
|
|
289
492
|
path.pop();
|
|
290
493
|
}
|
|
494
|
+
if (lengthDescriptor !== void 0) {
|
|
495
|
+
Object.defineProperty(clone2, "length", lengthDescriptor);
|
|
496
|
+
}
|
|
291
497
|
if (budget) visited.delete(obj);
|
|
292
498
|
return clone2;
|
|
293
499
|
}
|
|
@@ -339,6 +545,12 @@ function omitInternal(value, options, ignoreKeys, path, visited, budget) {
|
|
|
339
545
|
return clone;
|
|
340
546
|
}
|
|
341
547
|
__name(omitInternal, "omitInternal");
|
|
548
|
+
function arrayPathSegment(key) {
|
|
549
|
+
if (typeof key === "symbol") return key;
|
|
550
|
+
const index = Number(key);
|
|
551
|
+
return Number.isInteger(index) && index >= 0 && index < 4294967295 && String(index) === key ? index : key;
|
|
552
|
+
}
|
|
553
|
+
__name(arrayPathSegment, "arrayPathSegment");
|
|
342
554
|
function assignOwn(target, key, value) {
|
|
343
555
|
Object.defineProperty(target, key, {
|
|
344
556
|
value,
|
|
@@ -405,6 +617,8 @@ var DATE_MUTATORS = [
|
|
|
405
617
|
"setYear"
|
|
406
618
|
];
|
|
407
619
|
var mutationThrowers = /* @__PURE__ */ new Map();
|
|
620
|
+
var mapEntries = Map.prototype.entries;
|
|
621
|
+
var setValues = Set.prototype.values;
|
|
408
622
|
function mutationThrower(typeName, method) {
|
|
409
623
|
const key = `${typeName}.${method}`;
|
|
410
624
|
let thrower = mutationThrowers.get(key);
|
|
@@ -444,17 +658,19 @@ function deepFreeze(obj, visited = /* @__PURE__ */ new WeakSet()) {
|
|
|
444
658
|
return obj;
|
|
445
659
|
}
|
|
446
660
|
visited.add(obj);
|
|
447
|
-
const
|
|
448
|
-
|
|
449
|
-
|
|
661
|
+
const mutableBuiltInTag = mutableBuiltInTagWithoutInvokingAccessors(
|
|
662
|
+
obj
|
|
663
|
+
);
|
|
664
|
+
if (mutableBuiltInTag !== void 0) {
|
|
665
|
+
if (mutableBuiltInTag === "[object Date]") {
|
|
450
666
|
shadowMutators(obj, "Date", DATE_MUTATORS);
|
|
451
|
-
} else if (
|
|
667
|
+
} else if (mutableBuiltInTag === "[object Map]") {
|
|
452
668
|
for (const [key, value] of obj) {
|
|
453
669
|
deepFreeze(key, visited);
|
|
454
670
|
deepFreeze(value, visited);
|
|
455
671
|
}
|
|
456
672
|
shadowMutators(obj, "Map", ["set", "delete", "clear"]);
|
|
457
|
-
} else if (
|
|
673
|
+
} else if (mutableBuiltInTag === "[object Set]") {
|
|
458
674
|
for (const member of obj) {
|
|
459
675
|
deepFreeze(member, visited);
|
|
460
676
|
}
|
|
@@ -481,32 +697,63 @@ function cloneForVo(value, visited) {
|
|
|
481
697
|
return value;
|
|
482
698
|
}
|
|
483
699
|
const obj = value;
|
|
700
|
+
if (ArrayBuffer.isView(obj)) {
|
|
701
|
+
throwUnsupportedValueSemantics(
|
|
702
|
+
builtInTagWithoutInvokingAccessors(obj) ?? "[object ArrayBuffer view]"
|
|
703
|
+
);
|
|
704
|
+
}
|
|
484
705
|
if (visited.has(obj)) {
|
|
485
706
|
return visited.get(obj);
|
|
486
707
|
}
|
|
487
708
|
if (Array.isArray(obj)) {
|
|
709
|
+
if (!hasIntrinsicPrototypeChain(obj, "Array")) {
|
|
710
|
+
throwUnsupportedClassInstance();
|
|
711
|
+
}
|
|
488
712
|
const clone2 = new Array(obj.length);
|
|
489
713
|
visited.set(obj, clone2);
|
|
490
|
-
for (
|
|
491
|
-
|
|
714
|
+
for (const key of Reflect.ownKeys(obj)) {
|
|
715
|
+
if (key === "length") continue;
|
|
716
|
+
const descriptor = Object.getOwnPropertyDescriptor(obj, key);
|
|
717
|
+
if (descriptor === void 0) continue;
|
|
718
|
+
if (!("value" in descriptor)) {
|
|
719
|
+
throwUnsupportedAccessorProperty();
|
|
720
|
+
}
|
|
721
|
+
if (typeof key === "string" && !descriptor.enumerable) continue;
|
|
722
|
+
descriptor.value = cloneForVo(descriptor.value, visited);
|
|
723
|
+
Object.defineProperty(clone2, key, descriptor);
|
|
492
724
|
}
|
|
493
725
|
return clone2;
|
|
494
726
|
}
|
|
495
|
-
const tag =
|
|
496
|
-
if (
|
|
727
|
+
const tag = builtInTagWithoutInvokingAccessors(obj);
|
|
728
|
+
if (tag !== void 0) {
|
|
729
|
+
if (!hasIntrinsicPrototypeChain(obj)) {
|
|
730
|
+
throwUnsupportedClassInstance();
|
|
731
|
+
}
|
|
497
732
|
if (tag === "[object Map]") {
|
|
498
733
|
const clone2 = /* @__PURE__ */ new Map();
|
|
499
734
|
visited.set(obj, clone2);
|
|
500
|
-
for (const [key, entry] of
|
|
501
|
-
|
|
735
|
+
for (const [key, entry] of mapEntries.call(
|
|
736
|
+
obj
|
|
737
|
+
)) {
|
|
738
|
+
if (!isPrimitiveValue(key)) {
|
|
739
|
+
throw new TypeError(
|
|
740
|
+
"vo() Map keys must be primitive values to preserve value equality"
|
|
741
|
+
);
|
|
742
|
+
}
|
|
743
|
+
clone2.set(key, cloneForVo(entry, visited));
|
|
502
744
|
}
|
|
503
745
|
return clone2;
|
|
504
746
|
}
|
|
505
747
|
if (tag === "[object Set]") {
|
|
506
748
|
const clone2 = /* @__PURE__ */ new Set();
|
|
507
749
|
visited.set(obj, clone2);
|
|
508
|
-
for (const member of obj) {
|
|
509
|
-
|
|
750
|
+
for (const member of setValues.call(obj)) {
|
|
751
|
+
if (!isPrimitiveValue(member)) {
|
|
752
|
+
throw new TypeError(
|
|
753
|
+
"vo() Set members must be primitive values to preserve value equality"
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
clone2.add(member);
|
|
510
757
|
}
|
|
511
758
|
return clone2;
|
|
512
759
|
}
|
|
@@ -515,28 +762,67 @@ function cloneForVo(value, visited) {
|
|
|
515
762
|
`vo() cannot clone a ${tag.slice(8, -1)}: Value Objects are plain data`
|
|
516
763
|
);
|
|
517
764
|
}
|
|
765
|
+
if (tag === "[object Error]" || tag === "[object ArrayBuffer]" || tag === "[object SharedArrayBuffer]") {
|
|
766
|
+
throwUnsupportedValueSemantics(tag);
|
|
767
|
+
}
|
|
768
|
+
if (tag === "[object RegExp]") {
|
|
769
|
+
const regExp = obj;
|
|
770
|
+
if (regExp.global || regExp.sticky) {
|
|
771
|
+
throw new TypeError(
|
|
772
|
+
"vo() cannot accept a global or sticky RegExp: its lastIndex is mutable scan state, not an immutable value"
|
|
773
|
+
);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
518
776
|
const builtInClone = structuredClone(obj);
|
|
519
777
|
visited.set(obj, builtInClone);
|
|
520
778
|
return builtInClone;
|
|
521
779
|
}
|
|
522
|
-
const
|
|
780
|
+
const prototype = Object.getPrototypeOf(obj);
|
|
781
|
+
if (prototype !== null && (!isIntrinsicConstructorPrototype(prototype, "Object") || Object.getPrototypeOf(prototype) !== null)) {
|
|
782
|
+
throwUnsupportedClassInstance();
|
|
783
|
+
}
|
|
784
|
+
const clone = Object.create(prototype === null ? null : Object.prototype);
|
|
523
785
|
visited.set(obj, clone);
|
|
524
786
|
for (const key of Reflect.ownKeys(obj)) {
|
|
525
787
|
const descriptor = Object.getOwnPropertyDescriptor(obj, key);
|
|
526
|
-
if (
|
|
788
|
+
if (descriptor === void 0) continue;
|
|
789
|
+
if (!("value" in descriptor)) {
|
|
790
|
+
throwUnsupportedAccessorProperty();
|
|
791
|
+
}
|
|
792
|
+
if (typeof key === "string" && !descriptor.enumerable) continue;
|
|
527
793
|
Object.defineProperty(clone, key, {
|
|
528
|
-
value: cloneForVo(
|
|
529
|
-
obj[key],
|
|
530
|
-
visited
|
|
531
|
-
),
|
|
794
|
+
value: cloneForVo(descriptor.value, visited),
|
|
532
795
|
writable: true,
|
|
533
|
-
enumerable:
|
|
796
|
+
enumerable: descriptor.enumerable,
|
|
534
797
|
configurable: true
|
|
535
798
|
});
|
|
536
799
|
}
|
|
537
800
|
return clone;
|
|
538
801
|
}
|
|
539
802
|
__name(cloneForVo, "cloneForVo");
|
|
803
|
+
function throwUnsupportedClassInstance() {
|
|
804
|
+
throw new TypeError(
|
|
805
|
+
"vo() cannot clone custom class instances: Value Objects are plain data"
|
|
806
|
+
);
|
|
807
|
+
}
|
|
808
|
+
__name(throwUnsupportedClassInstance, "throwUnsupportedClassInstance");
|
|
809
|
+
function throwUnsupportedAccessorProperty() {
|
|
810
|
+
throw new TypeError(
|
|
811
|
+
"vo() cannot clone accessor properties: Value Objects are plain data"
|
|
812
|
+
);
|
|
813
|
+
}
|
|
814
|
+
__name(throwUnsupportedAccessorProperty, "throwUnsupportedAccessorProperty");
|
|
815
|
+
function throwUnsupportedValueSemantics(tag) {
|
|
816
|
+
const name = tag.startsWith("[object ") ? tag.slice(8, -1) : tag;
|
|
817
|
+
throw new TypeError(
|
|
818
|
+
`vo() cannot accept ${name} values: Value Objects require immutable value semantics`
|
|
819
|
+
);
|
|
820
|
+
}
|
|
821
|
+
__name(throwUnsupportedValueSemantics, "throwUnsupportedValueSemantics");
|
|
822
|
+
function isPrimitiveValue(value) {
|
|
823
|
+
return value === null || typeof value !== "object" && typeof value !== "function";
|
|
824
|
+
}
|
|
825
|
+
__name(isPrimitiveValue, "isPrimitiveValue");
|
|
540
826
|
function vo(t) {
|
|
541
827
|
return deepFreeze(cloneForVo(t, /* @__PURE__ */ new WeakMap()));
|
|
542
828
|
}
|
|
@@ -574,7 +860,7 @@ var ValueObject = class {
|
|
|
574
860
|
props;
|
|
575
861
|
/**
|
|
576
862
|
* Creates a new ValueObject.
|
|
577
|
-
* The properties are deep-cloned
|
|
863
|
+
* The plain-data properties are deep-cloned and then deeply
|
|
578
864
|
* frozen, so the caller's own object graph is never frozen or mutated,
|
|
579
865
|
* and later mutation of the input does not bleed into the value object.
|
|
580
866
|
*
|
|
@@ -629,7 +915,19 @@ var ValueObject = class {
|
|
|
629
915
|
*/
|
|
630
916
|
clone(props) {
|
|
631
917
|
const Constructor = this.constructor;
|
|
632
|
-
|
|
918
|
+
const merged = { ...this.props, ...props || {} };
|
|
919
|
+
for (const key of Reflect.ownKeys(this.props)) {
|
|
920
|
+
const descriptor = Object.getOwnPropertyDescriptor(this.props, key);
|
|
921
|
+
if (descriptor === void 0 || descriptor.enumerable) continue;
|
|
922
|
+
if (props && Object.hasOwn(props, key)) continue;
|
|
923
|
+
Object.defineProperty(merged, key, {
|
|
924
|
+
value: this.props[key],
|
|
925
|
+
writable: true,
|
|
926
|
+
enumerable: false,
|
|
927
|
+
configurable: true
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
return new Constructor(merged);
|
|
633
931
|
}
|
|
634
932
|
/**
|
|
635
933
|
* Serializes the value object to its raw properties for JSON operations.
|
|
@@ -641,13 +939,33 @@ var ValueObject = class {
|
|
|
641
939
|
}
|
|
642
940
|
};
|
|
643
941
|
|
|
644
|
-
// src/aggregate/
|
|
645
|
-
var
|
|
646
|
-
var
|
|
647
|
-
function
|
|
648
|
-
|
|
942
|
+
// src/aggregate/clock.ts
|
|
943
|
+
var defaultClockFactory = /* @__PURE__ */ __name(() => /* @__PURE__ */ new Date(), "defaultClockFactory");
|
|
944
|
+
var currentClockFactory = defaultClockFactory;
|
|
945
|
+
function now() {
|
|
946
|
+
return currentClockFactory();
|
|
649
947
|
}
|
|
650
|
-
__name(
|
|
948
|
+
__name(now, "now");
|
|
949
|
+
function setClockFactory(factory) {
|
|
950
|
+
currentClockFactory = factory;
|
|
951
|
+
}
|
|
952
|
+
__name(setClockFactory, "setClockFactory");
|
|
953
|
+
function withClockFactory(factory, fn) {
|
|
954
|
+
const previous = currentClockFactory;
|
|
955
|
+
currentClockFactory = factory;
|
|
956
|
+
try {
|
|
957
|
+
const result = fn();
|
|
958
|
+
assertNotThenable(result, "withClockFactory");
|
|
959
|
+
return result;
|
|
960
|
+
} finally {
|
|
961
|
+
currentClockFactory = previous;
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
__name(withClockFactory, "withClockFactory");
|
|
965
|
+
function resetClockFactory() {
|
|
966
|
+
currentClockFactory = defaultClockFactory;
|
|
967
|
+
}
|
|
968
|
+
__name(resetClockFactory, "resetClockFactory");
|
|
651
969
|
function assertNotThenable(result, helperName) {
|
|
652
970
|
if (result !== null && (typeof result === "object" || typeof result === "function") && typeof result.then === "function") {
|
|
653
971
|
throw new Error(
|
|
@@ -656,6 +974,14 @@ function assertNotThenable(result, helperName) {
|
|
|
656
974
|
}
|
|
657
975
|
}
|
|
658
976
|
__name(assertNotThenable, "assertNotThenable");
|
|
977
|
+
|
|
978
|
+
// src/aggregate/domain-event.ts
|
|
979
|
+
var defaultEventIdFactory = /* @__PURE__ */ __name(() => crypto.randomUUID(), "defaultEventIdFactory");
|
|
980
|
+
var currentEventIdFactory = defaultEventIdFactory;
|
|
981
|
+
function setEventIdFactory(factory) {
|
|
982
|
+
currentEventIdFactory = factory;
|
|
983
|
+
}
|
|
984
|
+
__name(setEventIdFactory, "setEventIdFactory");
|
|
659
985
|
function withEventIdFactory(factory, fn) {
|
|
660
986
|
const previous = currentEventIdFactory;
|
|
661
987
|
currentEventIdFactory = factory;
|
|
@@ -672,45 +998,46 @@ function resetEventIdFactory() {
|
|
|
672
998
|
currentEventIdFactory = defaultEventIdFactory;
|
|
673
999
|
}
|
|
674
1000
|
__name(resetEventIdFactory, "resetEventIdFactory");
|
|
675
|
-
var defaultClockFactory = /* @__PURE__ */ __name(() => /* @__PURE__ */ new Date(), "defaultClockFactory");
|
|
676
|
-
var currentClockFactory = defaultClockFactory;
|
|
677
|
-
function setClockFactory(factory) {
|
|
678
|
-
currentClockFactory = factory;
|
|
679
|
-
}
|
|
680
|
-
__name(setClockFactory, "setClockFactory");
|
|
681
|
-
function withClockFactory(factory, fn) {
|
|
682
|
-
const previous = currentClockFactory;
|
|
683
|
-
currentClockFactory = factory;
|
|
684
|
-
try {
|
|
685
|
-
const result = fn();
|
|
686
|
-
assertNotThenable(result, "withClockFactory");
|
|
687
|
-
return result;
|
|
688
|
-
} finally {
|
|
689
|
-
currentClockFactory = previous;
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
__name(withClockFactory, "withClockFactory");
|
|
693
|
-
function resetClockFactory() {
|
|
694
|
-
currentClockFactory = defaultClockFactory;
|
|
695
|
-
}
|
|
696
|
-
__name(resetClockFactory, "resetClockFactory");
|
|
697
1001
|
function createDomainEvent(type, payload, options) {
|
|
698
1002
|
const event = {
|
|
699
1003
|
eventId: options?.eventId ?? currentEventIdFactory(),
|
|
700
1004
|
type,
|
|
701
1005
|
aggregateId: options?.aggregateId,
|
|
702
1006
|
aggregateType: options?.aggregateType,
|
|
703
|
-
|
|
704
|
-
//
|
|
705
|
-
//
|
|
706
|
-
|
|
1007
|
+
// Defensive copies throughout: the deep-freeze below must never
|
|
1008
|
+
// reach the caller's own object graph. Without the clone, passing
|
|
1009
|
+
// (parts of) live aggregate state as payload, or reusing a metadata
|
|
1010
|
+
// object across events, would freeze the caller's objects in place;
|
|
1011
|
+
// the next mutation then throws far away from the cause. Same
|
|
1012
|
+
// ownership contract as `vo()` and the occurredAt copy.
|
|
1013
|
+
payload: cloneOwnedEventData(payload, "payload"),
|
|
1014
|
+
occurredAt: options?.occurredAt ? new Date(options.occurredAt.getTime()) : now(),
|
|
707
1015
|
version: options?.version ?? 1,
|
|
708
1016
|
aggregateVersion: options?.aggregateVersion,
|
|
709
|
-
metadata: options?.metadata
|
|
1017
|
+
metadata: cloneOwnedEventData(options?.metadata, "metadata")
|
|
710
1018
|
};
|
|
711
1019
|
return deepFreeze(event);
|
|
712
1020
|
}
|
|
713
1021
|
__name(createDomainEvent, "createDomainEvent");
|
|
1022
|
+
function cloneOwnedEventData(value, field) {
|
|
1023
|
+
if (typeof value === "function") {
|
|
1024
|
+
throw new TypeError(
|
|
1025
|
+
`createDomainEvent: ${field} must not be a function: domain events are plain data`
|
|
1026
|
+
);
|
|
1027
|
+
}
|
|
1028
|
+
if (value === null || typeof value !== "object") {
|
|
1029
|
+
return value;
|
|
1030
|
+
}
|
|
1031
|
+
try {
|
|
1032
|
+
return structuredClone(value);
|
|
1033
|
+
} catch (cause) {
|
|
1034
|
+
throw new TypeError(
|
|
1035
|
+
`createDomainEvent: ${field} must be plain, structured-cloneable data (no functions, Promises, or WeakMap/WeakSet values): domain events are plain data`,
|
|
1036
|
+
{ cause }
|
|
1037
|
+
);
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
__name(cloneOwnedEventData, "cloneOwnedEventData");
|
|
714
1041
|
function copyMetadata(sourceEvent, additionalMetadata) {
|
|
715
1042
|
return {
|
|
716
1043
|
...sourceEvent.metadata ?? {},
|
|
@@ -742,93 +1069,275 @@ function sameVersion(a, b) {
|
|
|
742
1069
|
return a.id === b.id && a.version === b.version;
|
|
743
1070
|
}
|
|
744
1071
|
__name(sameVersion, "sameVersion");
|
|
745
|
-
|
|
746
|
-
// src/entity/entity.ts
|
|
747
|
-
var Entity = class {
|
|
1072
|
+
var DomainError = class extends BaseError {
|
|
748
1073
|
static {
|
|
749
|
-
__name(this, "
|
|
1074
|
+
__name(this, "DomainError");
|
|
750
1075
|
}
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
* The state object is **shallowly frozen**: direct property writes
|
|
756
|
-
* (`entity.state.foo = …`) throw in strict mode, but writes to nested
|
|
757
|
-
* objects (`entity.state.address.zip = …`) bypass the freeze. For deep
|
|
758
|
-
* immutability either model nested data with `vo()` (which freezes
|
|
759
|
-
* deeply) or reach for a structural-sharing library like Immer at the
|
|
760
|
-
* App layer. The shallow contract is intentional: deep freezing on
|
|
761
|
-
* every state write is too expensive for hot paths, and DDD aggregates
|
|
762
|
-
* normally treat their own state as private (`Tell, Don't Ask`).
|
|
763
|
-
*/
|
|
764
|
-
get state() {
|
|
765
|
-
return this._state;
|
|
1076
|
+
};
|
|
1077
|
+
var InfrastructureError = class extends BaseError {
|
|
1078
|
+
static {
|
|
1079
|
+
__name(this, "InfrastructureError");
|
|
766
1080
|
}
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
* before the freeze, so the caller's own object stays mutable. A CLASS
|
|
775
|
-
* INSTANCE passed as state is an ownership transfer: it is frozen
|
|
776
|
-
* in place (a copy would strip its prototype). Do not keep mutating
|
|
777
|
-
* the instance after handing it to the entity. The same contract
|
|
778
|
-
* applies to {@link setState}.
|
|
779
|
-
*/
|
|
780
|
-
constructor(id, initialState) {
|
|
781
|
-
if (id === null || id === void 0) {
|
|
782
|
-
throw new Error("Entity ID cannot be null or undefined");
|
|
783
|
-
}
|
|
784
|
-
this.id = id;
|
|
785
|
-
this._state = freezeShallow(shallowCopyOwned(initialState));
|
|
786
|
-
this.validateState(this._state);
|
|
1081
|
+
};
|
|
1082
|
+
var MissingHandlerError = class extends BaseError {
|
|
1083
|
+
constructor(eventType, cause) {
|
|
1084
|
+
super(`Missing handler for event type: ${eventType}`, cause, {
|
|
1085
|
+
name: "MissingHandlerError"
|
|
1086
|
+
});
|
|
1087
|
+
this.eventType = eventType;
|
|
787
1088
|
}
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
* construction (from `Entity`'s constructor) and again on every
|
|
791
|
-
* `setState()` call. Throw to reject invalid state.
|
|
792
|
-
*
|
|
793
|
-
* **⚠️ Must not read subclass instance fields via `this`.** The
|
|
794
|
-
* constructor calls `validateState(initialState)` BEFORE the subclass's
|
|
795
|
-
* field initializers run, so `this.someField` is `undefined` at that
|
|
796
|
-
* point, a classic TypeScript/JavaScript constructor-ordering footgun.
|
|
797
|
-
* The `state` argument is the single source of truth; treat the method
|
|
798
|
-
* as pure with respect to `this`.
|
|
799
|
-
*
|
|
800
|
-
* If your invariants genuinely depend on per-instance configuration
|
|
801
|
-
* that isn't part of the state, pass that configuration into the state
|
|
802
|
-
* itself (DDD-canonical: the aggregate's state contains everything it
|
|
803
|
-
* needs) or perform the additional check after construction in a
|
|
804
|
-
* dedicated factory method.
|
|
805
|
-
*
|
|
806
|
-
* @param state - The state to validate
|
|
807
|
-
* @throws Error (or `DomainError` subclass) if validation fails
|
|
808
|
-
*/
|
|
809
|
-
validateState(_state) {
|
|
1089
|
+
static {
|
|
1090
|
+
__name(this, "MissingHandlerError");
|
|
810
1091
|
}
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
this.validateState(newState);
|
|
824
|
-
this._state = freezeShallow(shallowCopyOwned(newState));
|
|
1092
|
+
};
|
|
1093
|
+
var UnreplayableAggregateError = class extends BaseError {
|
|
1094
|
+
constructor(aggregateId, reason) {
|
|
1095
|
+
super(
|
|
1096
|
+
`Cannot replay history onto aggregate ${aggregateId}: ${reason}. Reconstitute on a fresh instance (no factory-recorded events, no unpersisted mutations), or markPersisted the aggregate before a catch-up replay.`,
|
|
1097
|
+
void 0,
|
|
1098
|
+
{ name: "UnreplayableAggregateError" }
|
|
1099
|
+
);
|
|
1100
|
+
this.aggregateId = aggregateId;
|
|
1101
|
+
}
|
|
1102
|
+
static {
|
|
1103
|
+
__name(this, "UnreplayableAggregateError");
|
|
825
1104
|
}
|
|
826
1105
|
};
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
1106
|
+
var EventHarvestError = class extends BaseError {
|
|
1107
|
+
constructor(message, eventType) {
|
|
1108
|
+
super(message, void 0, { name: "EventHarvestError" });
|
|
1109
|
+
this.eventType = eventType;
|
|
830
1110
|
}
|
|
831
|
-
|
|
1111
|
+
static {
|
|
1112
|
+
__name(this, "EventHarvestError");
|
|
1113
|
+
}
|
|
1114
|
+
};
|
|
1115
|
+
var UnregisteredHandlerError = class extends BaseError {
|
|
1116
|
+
static {
|
|
1117
|
+
__name(this, "UnregisteredHandlerError");
|
|
1118
|
+
}
|
|
1119
|
+
busKind;
|
|
1120
|
+
messageType;
|
|
1121
|
+
constructor(options) {
|
|
1122
|
+
super(
|
|
1123
|
+
`No handler registered for ${options.busKind} type: ${options.messageType}`,
|
|
1124
|
+
void 0,
|
|
1125
|
+
{ name: "UnregisteredHandlerError" }
|
|
1126
|
+
);
|
|
1127
|
+
this.busKind = options.busKind;
|
|
1128
|
+
this.messageType = options.messageType;
|
|
1129
|
+
}
|
|
1130
|
+
};
|
|
1131
|
+
var UnenrolledChangesError = class extends BaseError {
|
|
1132
|
+
constructor(aggregateId) {
|
|
1133
|
+
super(
|
|
1134
|
+
`Aggregate ${aggregateId} was loaded in this unit of work and has pending events, but was never enrolled (no save), so its events would be silently dropped. Call repository.save(aggregate), and ensure save() calls session.enrollSaved before the row write.`,
|
|
1135
|
+
void 0,
|
|
1136
|
+
{ name: "UnenrolledChangesError" }
|
|
1137
|
+
);
|
|
1138
|
+
this.aggregateId = aggregateId;
|
|
1139
|
+
}
|
|
1140
|
+
static {
|
|
1141
|
+
__name(this, "UnenrolledChangesError");
|
|
1142
|
+
}
|
|
1143
|
+
};
|
|
1144
|
+
var AggregateDeletedError = class extends BaseError {
|
|
1145
|
+
constructor(aggregateId) {
|
|
1146
|
+
super(
|
|
1147
|
+
`Aggregate ${aggregateId} was deleted in this unit of work and cannot be saved or registered again. Deletion is final within an operation; if the aggregate must live, do not delete it.`,
|
|
1148
|
+
void 0,
|
|
1149
|
+
{ name: "AggregateDeletedError" }
|
|
1150
|
+
);
|
|
1151
|
+
this.aggregateId = aggregateId;
|
|
1152
|
+
}
|
|
1153
|
+
static {
|
|
1154
|
+
__name(this, "AggregateDeletedError");
|
|
1155
|
+
}
|
|
1156
|
+
};
|
|
1157
|
+
var AggregateNotFoundError = class extends InfrastructureError {
|
|
1158
|
+
static {
|
|
1159
|
+
__name(this, "AggregateNotFoundError");
|
|
1160
|
+
}
|
|
1161
|
+
aggregateType;
|
|
1162
|
+
id;
|
|
1163
|
+
constructor(options) {
|
|
1164
|
+
super(
|
|
1165
|
+
`Aggregate not found: ${options.aggregateType}(${options.id})`,
|
|
1166
|
+
options.cause,
|
|
1167
|
+
{ name: "AggregateNotFoundError" }
|
|
1168
|
+
);
|
|
1169
|
+
this.aggregateType = options.aggregateType;
|
|
1170
|
+
this.id = options.id;
|
|
1171
|
+
}
|
|
1172
|
+
};
|
|
1173
|
+
var DuplicateAggregateError = class extends InfrastructureError {
|
|
1174
|
+
static {
|
|
1175
|
+
__name(this, "DuplicateAggregateError");
|
|
1176
|
+
}
|
|
1177
|
+
aggregateType;
|
|
1178
|
+
aggregateId;
|
|
1179
|
+
constructor(options) {
|
|
1180
|
+
super(
|
|
1181
|
+
`Duplicate aggregate: ${options.aggregateType}(${options.aggregateId}) already exists`,
|
|
1182
|
+
options.cause,
|
|
1183
|
+
{ name: "DuplicateAggregateError" }
|
|
1184
|
+
);
|
|
1185
|
+
this.aggregateType = options.aggregateType;
|
|
1186
|
+
this.aggregateId = options.aggregateId;
|
|
1187
|
+
}
|
|
1188
|
+
};
|
|
1189
|
+
var SnapshotSchemaMismatchError = class extends InfrastructureError {
|
|
1190
|
+
static {
|
|
1191
|
+
__name(this, "SnapshotSchemaMismatchError");
|
|
1192
|
+
}
|
|
1193
|
+
aggregateType;
|
|
1194
|
+
aggregateId;
|
|
1195
|
+
expectedSchemaVersion;
|
|
1196
|
+
actualSchemaVersion;
|
|
1197
|
+
constructor(options) {
|
|
1198
|
+
super(
|
|
1199
|
+
`Snapshot schema mismatch on ${options.aggregateType}(${options.aggregateId}): the aggregate expects snapshot schema ${options.expectedSchemaVersion}, the stored snapshot carries ${options.actualSchemaVersion}. Override migrateSnapshotState to upgrade old snapshots, or discard the snapshot and refold from the full event stream.`,
|
|
1200
|
+
void 0,
|
|
1201
|
+
{ name: "SnapshotSchemaMismatchError" }
|
|
1202
|
+
);
|
|
1203
|
+
this.aggregateType = options.aggregateType;
|
|
1204
|
+
this.aggregateId = options.aggregateId;
|
|
1205
|
+
this.expectedSchemaVersion = options.expectedSchemaVersion;
|
|
1206
|
+
this.actualSchemaVersion = options.actualSchemaVersion;
|
|
1207
|
+
}
|
|
1208
|
+
};
|
|
1209
|
+
var ConcurrencyConflictError = class extends InfrastructureError {
|
|
1210
|
+
static {
|
|
1211
|
+
__name(this, "ConcurrencyConflictError");
|
|
1212
|
+
}
|
|
1213
|
+
/**
|
|
1214
|
+
* Marks this error as retryable so `isRetryable(err)` returns
|
|
1215
|
+
* true. The canonical OCC pattern is to reload the aggregate, re-apply
|
|
1216
|
+
* the use case, and retry on this exception.
|
|
1217
|
+
*/
|
|
1218
|
+
retryable = true;
|
|
1219
|
+
aggregateType;
|
|
1220
|
+
aggregateId;
|
|
1221
|
+
expectedVersion;
|
|
1222
|
+
actualVersion;
|
|
1223
|
+
constructor(options) {
|
|
1224
|
+
super(
|
|
1225
|
+
`Concurrency conflict on ${options.aggregateType}(${options.aggregateId}): expected version ${options.expectedVersion}, actual ${options.actualVersion}`,
|
|
1226
|
+
options.cause,
|
|
1227
|
+
{ name: "ConcurrencyConflictError" }
|
|
1228
|
+
);
|
|
1229
|
+
this.aggregateType = options.aggregateType;
|
|
1230
|
+
this.aggregateId = options.aggregateId;
|
|
1231
|
+
this.expectedVersion = options.expectedVersion;
|
|
1232
|
+
this.actualVersion = options.actualVersion;
|
|
1233
|
+
}
|
|
1234
|
+
};
|
|
1235
|
+
|
|
1236
|
+
// src/entity/entity.ts
|
|
1237
|
+
var Entity = class {
|
|
1238
|
+
static {
|
|
1239
|
+
__name(this, "Entity");
|
|
1240
|
+
}
|
|
1241
|
+
id;
|
|
1242
|
+
/**
|
|
1243
|
+
* Returns the current state of the entity.
|
|
1244
|
+
*
|
|
1245
|
+
* By default the state object is **shallowly frozen**: direct property
|
|
1246
|
+
* writes (`entity.state.foo = …`) throw in strict mode, but writes to
|
|
1247
|
+
* nested objects (`entity.state.address.zip = …`) bypass the freeze.
|
|
1248
|
+
* For deep immutability either enable the opt-in
|
|
1249
|
+
* {@link EntityConfig.deepFreezeState} (plain-data states only), model
|
|
1250
|
+
* nested data with `vo()` (which freezes deeply), or reach for a
|
|
1251
|
+
* structural-sharing library like Immer at the App layer. The shallow
|
|
1252
|
+
* default is intentional: deep freezing on every state write is too
|
|
1253
|
+
* expensive for hot paths, and DDD aggregates normally treat their own
|
|
1254
|
+
* state as private (`Tell, Don't Ask`).
|
|
1255
|
+
*/
|
|
1256
|
+
get state() {
|
|
1257
|
+
return this._state;
|
|
1258
|
+
}
|
|
1259
|
+
/**
|
|
1260
|
+
* The state is 'protected' so that only the subclass can modify it.
|
|
1261
|
+
* Subclasses can mutate this directly or use helper methods; direct
|
|
1262
|
+
* assignments should freeze through {@link freezeState} so the
|
|
1263
|
+
* configured freeze mode is honored.
|
|
1264
|
+
*/
|
|
1265
|
+
_state;
|
|
1266
|
+
_deepFreezeState;
|
|
1267
|
+
/**
|
|
1268
|
+
* **State ownership.** Plain-object and array states are shallow-copied
|
|
1269
|
+
* before the freeze, so the caller's own object stays mutable. A CLASS
|
|
1270
|
+
* INSTANCE passed as state is an ownership transfer: it is frozen
|
|
1271
|
+
* in place (a copy would strip its prototype). Do not keep mutating
|
|
1272
|
+
* the instance after handing it to the entity. The same contract
|
|
1273
|
+
* applies to {@link setState}. With
|
|
1274
|
+
* {@link EntityConfig.deepFreezeState} enabled, the ownership transfer
|
|
1275
|
+
* widens to the whole graph: NESTED objects are frozen in place too.
|
|
1276
|
+
*/
|
|
1277
|
+
constructor(id, initialState, config) {
|
|
1278
|
+
if (id === null || id === void 0) {
|
|
1279
|
+
throw new Error("Entity ID cannot be null or undefined");
|
|
1280
|
+
}
|
|
1281
|
+
this.id = id;
|
|
1282
|
+
this._deepFreezeState = config?.deepFreezeState ?? false;
|
|
1283
|
+
this._state = this.freezeState(shallowCopyOwned(initialState));
|
|
1284
|
+
this.validateState(this._state);
|
|
1285
|
+
}
|
|
1286
|
+
/**
|
|
1287
|
+
* Freezes a state value according to this entity's configured freeze
|
|
1288
|
+
* mode: the default shallow freeze, or `deepFreeze` when
|
|
1289
|
+
* {@link EntityConfig.deepFreezeState} was enabled at construction.
|
|
1290
|
+
* Subclass code that assigns `this._state` directly (bypassing
|
|
1291
|
+
* `setState`) must freeze through this method, not `freezeShallow`,
|
|
1292
|
+
* or the opt-in silently degrades to shallow for that path.
|
|
1293
|
+
*/
|
|
1294
|
+
freezeState(value) {
|
|
1295
|
+
return this._deepFreezeState ? deepFreeze(value) : freezeShallow(value);
|
|
1296
|
+
}
|
|
1297
|
+
/**
|
|
1298
|
+
* Optional validation hook to ensure state invariants. Called during
|
|
1299
|
+
* construction (from `Entity`'s constructor) and again on every
|
|
1300
|
+
* `setState()` call. Throw to reject invalid state.
|
|
1301
|
+
*
|
|
1302
|
+
* **⚠️ Must not read subclass instance fields via `this`.** The
|
|
1303
|
+
* constructor calls `validateState(initialState)` BEFORE the subclass's
|
|
1304
|
+
* field initializers run, so `this.someField` is `undefined` at that
|
|
1305
|
+
* point, a classic TypeScript/JavaScript constructor-ordering footgun.
|
|
1306
|
+
* The `state` argument is the single source of truth; treat the method
|
|
1307
|
+
* as pure with respect to `this`.
|
|
1308
|
+
*
|
|
1309
|
+
* If your invariants genuinely depend on per-instance configuration
|
|
1310
|
+
* that isn't part of the state, pass that configuration into the state
|
|
1311
|
+
* itself (DDD-canonical: the aggregate's state contains everything it
|
|
1312
|
+
* needs) or perform the additional check after construction in a
|
|
1313
|
+
* dedicated factory method.
|
|
1314
|
+
*
|
|
1315
|
+
* @param state - The state to validate
|
|
1316
|
+
* @throws Error (or `DomainError` subclass) if validation fails
|
|
1317
|
+
*/
|
|
1318
|
+
validateState(_state) {
|
|
1319
|
+
}
|
|
1320
|
+
/**
|
|
1321
|
+
* Sets the state of the entity.
|
|
1322
|
+
* This is a convenience method for state mutations.
|
|
1323
|
+
* Automatically validates the newState using `validateState()`.
|
|
1324
|
+
*
|
|
1325
|
+
* Plain-object and array states are shallow-copied before the freeze
|
|
1326
|
+
* (the caller's object stays mutable); a class-instance state is an
|
|
1327
|
+
* ownership transfer and is frozen in place; see the constructor.
|
|
1328
|
+
*
|
|
1329
|
+
* @param newState - The new state
|
|
1330
|
+
*/
|
|
1331
|
+
setState(newState) {
|
|
1332
|
+
this.validateState(newState);
|
|
1333
|
+
this._state = this.freezeState(shallowCopyOwned(newState));
|
|
1334
|
+
}
|
|
1335
|
+
};
|
|
1336
|
+
function freezeShallow(value) {
|
|
1337
|
+
if (value !== null && typeof value === "object") {
|
|
1338
|
+
return Object.freeze(value);
|
|
1339
|
+
}
|
|
1340
|
+
return value;
|
|
832
1341
|
}
|
|
833
1342
|
__name(freezeShallow, "freezeShallow");
|
|
834
1343
|
function shallowCopyOwned(value) {
|
|
@@ -1037,14 +1546,66 @@ var BaseAggregate = class extends Entity {
|
|
|
1037
1546
|
*
|
|
1038
1547
|
* The state is converted via {@link toSnapshotState}; the default
|
|
1039
1548
|
* requires plain, serialisable data and fails fast otherwise.
|
|
1549
|
+
*
|
|
1550
|
+
* `snapshotAt` is read from the kit's swappable clock, the same one
|
|
1551
|
+
* `createDomainEvent` stamps `occurredAt` from, so
|
|
1552
|
+
* `setClockFactory` / `withClockFactory` pin snapshot timestamps in
|
|
1553
|
+
* deterministic tests too. `schemaVersion` is stamped from
|
|
1554
|
+
* {@link snapshotSchemaVersion} so a later restore can detect
|
|
1555
|
+
* snapshots written against an older `TSnapshotState` shape.
|
|
1040
1556
|
*/
|
|
1041
1557
|
createSnapshot() {
|
|
1042
1558
|
return {
|
|
1043
1559
|
state: this.toSnapshotState(this._state),
|
|
1044
1560
|
version: this.version,
|
|
1045
|
-
snapshotAt:
|
|
1561
|
+
snapshotAt: now(),
|
|
1562
|
+
schemaVersion: this.snapshotSchemaVersion
|
|
1046
1563
|
};
|
|
1047
1564
|
}
|
|
1565
|
+
/**
|
|
1566
|
+
* Schema version of the shape {@link toSnapshotState} produces.
|
|
1567
|
+
* Defaults to `1`. Bump it whenever `TSnapshotState` changes
|
|
1568
|
+
* incompatibly (renamed or removed fields, changed representations):
|
|
1569
|
+
* `createSnapshot` stamps it onto every snapshot, and the restore
|
|
1570
|
+
* paths compare it, so an outdated stored snapshot surfaces as a
|
|
1571
|
+
* `SnapshotSchemaMismatchError` at restore time (or is upgraded via
|
|
1572
|
+
* {@link migrateSnapshotState}) instead of crashing on the first
|
|
1573
|
+
* method call much later.
|
|
1574
|
+
*/
|
|
1575
|
+
snapshotSchemaVersion = 1;
|
|
1576
|
+
/**
|
|
1577
|
+
* Resolves a stored snapshot's state against the aggregate's current
|
|
1578
|
+
* snapshot schema: pass-through when the versions match (a missing
|
|
1579
|
+
* `schemaVersion` counts as `1`, the pre-versioning era), otherwise
|
|
1580
|
+
* routed through {@link migrateSnapshotState}. Called by both restore
|
|
1581
|
+
* paths BEFORE anything is assigned, so a rejected snapshot leaves
|
|
1582
|
+
* the aggregate untouched.
|
|
1583
|
+
*/
|
|
1584
|
+
resolveSnapshotState(snapshot) {
|
|
1585
|
+
const storedSchemaVersion = snapshot.schemaVersion ?? 1;
|
|
1586
|
+
if (storedSchemaVersion === this.snapshotSchemaVersion) {
|
|
1587
|
+
return snapshot.state;
|
|
1588
|
+
}
|
|
1589
|
+
return this.migrateSnapshotState(snapshot.state, storedSchemaVersion);
|
|
1590
|
+
}
|
|
1591
|
+
/**
|
|
1592
|
+
* Upgrade hook for snapshots written against an older
|
|
1593
|
+
* `TSnapshotState` shape. Receives the stored state as `unknown`
|
|
1594
|
+
* (its shape is, by definition, not the current `TSnapshotState`)
|
|
1595
|
+
* plus the schema version it was written with, and returns the
|
|
1596
|
+
* current shape. The default rejects with
|
|
1597
|
+
* `SnapshotSchemaMismatchError`: discard-and-refold from the full
|
|
1598
|
+
* event stream is the safe default strategy; override this only when
|
|
1599
|
+
* upgrading in place is cheaper than refolding.
|
|
1600
|
+
*/
|
|
1601
|
+
migrateSnapshotState(_stored, storedSchemaVersion) {
|
|
1602
|
+
throw new SnapshotSchemaMismatchError({
|
|
1603
|
+
aggregateType: this.aggregateType,
|
|
1604
|
+
aggregateId: String(this.id),
|
|
1605
|
+
expectedSchemaVersion: this.snapshotSchemaVersion,
|
|
1606
|
+
actualSchemaVersion: storedSchemaVersion
|
|
1607
|
+
});
|
|
1608
|
+
}
|
|
1048
1609
|
/**
|
|
1049
1610
|
* Converts live aggregate state into the plain-data shape stored in a
|
|
1050
1611
|
* snapshot. The default validates that the state graph is plain,
|
|
@@ -1213,7 +1774,7 @@ var AggregateRoot = class extends BaseAggregate {
|
|
|
1213
1774
|
*/
|
|
1214
1775
|
_hasBaseline = false;
|
|
1215
1776
|
constructor(id, initialState, config) {
|
|
1216
|
-
super(id, initialState);
|
|
1777
|
+
super(id, initialState, config);
|
|
1217
1778
|
this._autoVersionBump = config?.autoVersionBump ?? false;
|
|
1218
1779
|
}
|
|
1219
1780
|
/**
|
|
@@ -1368,6 +1929,14 @@ var AggregateRoot = class extends BaseAggregate {
|
|
|
1368
1929
|
* Sets the state and optionally bumps the version automatically.
|
|
1369
1930
|
* Validates `newState` via `validateState()`.
|
|
1370
1931
|
*
|
|
1932
|
+
* **State the OCC intent explicitly.** With neither the per-call
|
|
1933
|
+
* `bumpVersion` argument nor an `autoVersionBump` config, the version
|
|
1934
|
+
* does not move, and an un-bumped mutation can be silently overwritten
|
|
1935
|
+
* by a concurrent writer (see the {@link AggregateConfig.autoVersionBump}
|
|
1936
|
+
* warning for the mechanics). Prefer `commit()` for domain mutations,
|
|
1937
|
+
* or pass `bumpVersion` explicitly; relying on the implicit default is
|
|
1938
|
+
* deprecated and v3 makes the argument required.
|
|
1939
|
+
*
|
|
1371
1940
|
* @param newState - The new state
|
|
1372
1941
|
* @param bumpVersion - Whether to bump the version (defaults to autoVersionBump config)
|
|
1373
1942
|
*/
|
|
@@ -1386,9 +1955,9 @@ var AggregateRoot = class extends BaseAggregate {
|
|
|
1386
1955
|
* @param snapshot - The snapshot to restore from
|
|
1387
1956
|
*/
|
|
1388
1957
|
restoreFromSnapshot(snapshot) {
|
|
1389
|
-
const restored = this.fromSnapshotState(snapshot
|
|
1958
|
+
const restored = this.fromSnapshotState(this.resolveSnapshotState(snapshot));
|
|
1390
1959
|
this.validateState(restored);
|
|
1391
|
-
this._state =
|
|
1960
|
+
this._state = this.freezeState(restored);
|
|
1392
1961
|
this.markRestored(snapshot.version);
|
|
1393
1962
|
}
|
|
1394
1963
|
};
|
|
@@ -1419,153 +1988,37 @@ function computeChangedKeys(baseline, current) {
|
|
|
1419
1988
|
return dirty;
|
|
1420
1989
|
}
|
|
1421
1990
|
__name(computeChangedKeys, "computeChangedKeys");
|
|
1422
|
-
var
|
|
1423
|
-
static {
|
|
1424
|
-
__name(this, "DomainError");
|
|
1425
|
-
}
|
|
1426
|
-
};
|
|
1427
|
-
var InfrastructureError = class extends BaseError {
|
|
1428
|
-
static {
|
|
1429
|
-
__name(this, "InfrastructureError");
|
|
1430
|
-
}
|
|
1431
|
-
};
|
|
1432
|
-
var MissingHandlerError = class extends BaseError {
|
|
1433
|
-
constructor(eventType, cause) {
|
|
1434
|
-
super(`Missing handler for event type: ${eventType}`, cause, {
|
|
1435
|
-
name: "MissingHandlerError"
|
|
1436
|
-
});
|
|
1437
|
-
this.eventType = eventType;
|
|
1438
|
-
}
|
|
1991
|
+
var EventSourcedAggregate = class extends BaseAggregate {
|
|
1439
1992
|
static {
|
|
1440
|
-
__name(this, "
|
|
1993
|
+
__name(this, "EventSourcedAggregate");
|
|
1441
1994
|
}
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1995
|
+
/**
|
|
1996
|
+
* Validates an event before it is applied. Default is no-op.
|
|
1997
|
+
* Subclasses override to throw a concrete `DomainError` subclass when
|
|
1998
|
+
* the event violates an invariant in the current state.
|
|
1999
|
+
*/
|
|
2000
|
+
validateEvent(_event) {
|
|
1447
2001
|
}
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
`Aggregate ${aggregateId} was deleted in this unit of work and cannot be saved or registered again. Deletion is final within an operation; if the aggregate must live, do not delete it.`,
|
|
1469
|
-
void 0,
|
|
1470
|
-
{ name: "AggregateDeletedError" }
|
|
1471
|
-
);
|
|
1472
|
-
this.aggregateId = aggregateId;
|
|
1473
|
-
}
|
|
1474
|
-
static {
|
|
1475
|
-
__name(this, "AggregateDeletedError");
|
|
1476
|
-
}
|
|
1477
|
-
};
|
|
1478
|
-
var AggregateNotFoundError = class extends InfrastructureError {
|
|
1479
|
-
static {
|
|
1480
|
-
__name(this, "AggregateNotFoundError");
|
|
1481
|
-
}
|
|
1482
|
-
aggregateType;
|
|
1483
|
-
id;
|
|
1484
|
-
constructor(options) {
|
|
1485
|
-
super(
|
|
1486
|
-
`Aggregate not found: ${options.aggregateType}(${options.id})`,
|
|
1487
|
-
options.cause,
|
|
1488
|
-
{ name: "AggregateNotFoundError" }
|
|
1489
|
-
);
|
|
1490
|
-
this.aggregateType = options.aggregateType;
|
|
1491
|
-
this.id = options.id;
|
|
1492
|
-
}
|
|
1493
|
-
};
|
|
1494
|
-
var DuplicateAggregateError = class extends InfrastructureError {
|
|
1495
|
-
static {
|
|
1496
|
-
__name(this, "DuplicateAggregateError");
|
|
1497
|
-
}
|
|
1498
|
-
aggregateType;
|
|
1499
|
-
aggregateId;
|
|
1500
|
-
constructor(options) {
|
|
1501
|
-
super(
|
|
1502
|
-
`Duplicate aggregate: ${options.aggregateType}(${options.aggregateId}) already exists`,
|
|
1503
|
-
options.cause,
|
|
1504
|
-
{ name: "DuplicateAggregateError" }
|
|
1505
|
-
);
|
|
1506
|
-
this.aggregateType = options.aggregateType;
|
|
1507
|
-
this.aggregateId = options.aggregateId;
|
|
1508
|
-
}
|
|
1509
|
-
};
|
|
1510
|
-
var ConcurrencyConflictError = class extends InfrastructureError {
|
|
1511
|
-
static {
|
|
1512
|
-
__name(this, "ConcurrencyConflictError");
|
|
1513
|
-
}
|
|
1514
|
-
/**
|
|
1515
|
-
* Marks this error as retryable so `isRetryable(err)` returns
|
|
1516
|
-
* true. The canonical OCC pattern is to reload the aggregate, re-apply
|
|
1517
|
-
* the use case, and retry on this exception.
|
|
1518
|
-
*/
|
|
1519
|
-
retryable = true;
|
|
1520
|
-
aggregateType;
|
|
1521
|
-
aggregateId;
|
|
1522
|
-
expectedVersion;
|
|
1523
|
-
actualVersion;
|
|
1524
|
-
constructor(options) {
|
|
1525
|
-
super(
|
|
1526
|
-
`Concurrency conflict on ${options.aggregateType}(${options.aggregateId}): expected version ${options.expectedVersion}, actual ${options.actualVersion}`,
|
|
1527
|
-
options.cause,
|
|
1528
|
-
{ name: "ConcurrencyConflictError" }
|
|
1529
|
-
);
|
|
1530
|
-
this.aggregateType = options.aggregateType;
|
|
1531
|
-
this.aggregateId = options.aggregateId;
|
|
1532
|
-
this.expectedVersion = options.expectedVersion;
|
|
1533
|
-
this.actualVersion = options.actualVersion;
|
|
1534
|
-
}
|
|
1535
|
-
};
|
|
1536
|
-
|
|
1537
|
-
// src/aggregate/event-sourced-aggregate.ts
|
|
1538
|
-
var EventSourcedAggregate = class extends BaseAggregate {
|
|
1539
|
-
static {
|
|
1540
|
-
__name(this, "EventSourcedAggregate");
|
|
1541
|
-
}
|
|
1542
|
-
/**
|
|
1543
|
-
* Validates an event before it is applied. Default is no-op.
|
|
1544
|
-
* Subclasses override to throw a concrete `DomainError` subclass when
|
|
1545
|
-
* the event violates an invariant in the current state.
|
|
1546
|
-
*/
|
|
1547
|
-
validateEvent(_event) {
|
|
1548
|
-
}
|
|
1549
|
-
/**
|
|
1550
|
-
* Applies an event: validates, locates the handler, computes the next
|
|
1551
|
-
* state, then commits state + pending event + version bump atomically.
|
|
1552
|
-
*
|
|
1553
|
-
* Throws `DomainError` (or a subclass) on validation failure.
|
|
1554
|
-
* Throws `MissingHandlerError` if no handler is registered for `event.type`.
|
|
1555
|
-
*
|
|
1556
|
-
* State is not mutated if any step throws: the handler is invoked into
|
|
1557
|
-
* a local and only assigned to `_state` once all checks pass.
|
|
1558
|
-
*
|
|
1559
|
-
* The method is generic in the event tag `K`, so concrete callers
|
|
1560
|
-
* (`this.apply(orderCreated)`) narrow to the literal tag and the
|
|
1561
|
-
* dispatched handler is typed as `Handler<TState, Extract<TEvent, { type: K }>>`,
|
|
1562
|
-
* with no `as` cast required at the call site.
|
|
1563
|
-
*
|
|
1564
|
-
* @param event - The domain event to apply
|
|
1565
|
-
* @param isNew - Whether the event is new (needs persisting) or replayed from history
|
|
1566
|
-
*/
|
|
1567
|
-
apply(event, isNew = true) {
|
|
1568
|
-
this.dispatchAndCommit(event, isNew);
|
|
2002
|
+
/**
|
|
2003
|
+
* Applies an event: validates, locates the handler, computes the next
|
|
2004
|
+
* state, then commits state + pending event + version bump atomically.
|
|
2005
|
+
*
|
|
2006
|
+
* Throws `DomainError` (or a subclass) on validation failure.
|
|
2007
|
+
* Throws `MissingHandlerError` if no handler is registered for `event.type`.
|
|
2008
|
+
*
|
|
2009
|
+
* State is not mutated if any step throws: the handler is invoked into
|
|
2010
|
+
* a local and only assigned to `_state` once all checks pass.
|
|
2011
|
+
*
|
|
2012
|
+
* The method is generic in the event tag `K`, so concrete callers
|
|
2013
|
+
* (`this.apply(orderCreated)`) narrow to the literal tag and the
|
|
2014
|
+
* dispatched handler is typed as `Handler<TState, Extract<TEvent, { type: K }>>`,
|
|
2015
|
+
* with no `as` cast required at the call site.
|
|
2016
|
+
*
|
|
2017
|
+
* @param event - The domain event to apply
|
|
2018
|
+
* @param isNew - Whether the event is new (needs persisting) or replayed from history
|
|
2019
|
+
*/
|
|
2020
|
+
apply(event, isNew = true) {
|
|
2021
|
+
this.dispatchAndCommit(event, isNew);
|
|
1569
2022
|
}
|
|
1570
2023
|
/**
|
|
1571
2024
|
* Internal dispatch path used by `apply()` and the replay methods
|
|
@@ -1581,7 +2034,7 @@ var EventSourcedAggregate = class extends BaseAggregate {
|
|
|
1581
2034
|
throw new MissingHandlerError(event.type);
|
|
1582
2035
|
}
|
|
1583
2036
|
const nextState = handler(this._state, event);
|
|
1584
|
-
this._state =
|
|
2037
|
+
this._state = this.freezeState(nextState);
|
|
1585
2038
|
if (isNew) {
|
|
1586
2039
|
this.addDomainEvent(event);
|
|
1587
2040
|
this.bumpVersion();
|
|
@@ -1600,11 +2053,24 @@ var EventSourcedAggregate = class extends BaseAggregate {
|
|
|
1600
2053
|
* which never bumps it; only the final `markRestored` advances it.)
|
|
1601
2054
|
*
|
|
1602
2055
|
* Version advances additively: the aggregate's pre-existing version plus
|
|
1603
|
-
* `history.length`. A fresh aggregate (v=0) loading 3 events ends at
|
|
1604
|
-
*
|
|
1605
|
-
*
|
|
2056
|
+
* `history.length`. A fresh aggregate (v=0) loading 3 events ends at
|
|
2057
|
+
* v=3; a PERSISTED aggregate at v=P (`persistedVersion === P`) catching
|
|
2058
|
+
* up on M newer events ends at v=P+M.
|
|
2059
|
+
*
|
|
2060
|
+
* **The replay target must be fresh or persisted.** An aggregate with
|
|
2061
|
+
* unflushed `pendingEvents`, or with an in-memory version that was
|
|
2062
|
+
* never persisted (a factory-created instance), throws
|
|
2063
|
+
* {@link UnreplayableAggregateError} BEFORE anything moves: replaying
|
|
2064
|
+
* onto it would `markRestored` a `persistedVersion` that counts
|
|
2065
|
+
* unpersisted history, flipping repository routing from INSERT to
|
|
2066
|
+
* UPDATE (or appending with a wrong expected version). The throw is
|
|
2067
|
+
* deliberate (crash-loud programming bug), never a `Result` `Err`, and
|
|
2068
|
+
* runs before the empty-history fast path so the misuse is caught
|
|
2069
|
+
* deterministically rather than only when the stream happens to be
|
|
2070
|
+
* non-empty.
|
|
1606
2071
|
*/
|
|
1607
2072
|
loadFromHistory(history) {
|
|
2073
|
+
this.assertReplayTargetFresh(true);
|
|
1608
2074
|
if (history.length === 0) return ok();
|
|
1609
2075
|
const previousState = this._state;
|
|
1610
2076
|
const startVersion = this.version;
|
|
@@ -1629,11 +2095,29 @@ var EventSourcedAggregate = class extends BaseAggregate {
|
|
|
1629
2095
|
* All-or-nothing: if any event mid-stream throws a `DomainError`, the
|
|
1630
2096
|
* aggregate is rolled back to its pre-call state + version. Partial
|
|
1631
2097
|
* restoration is never observable to the caller.
|
|
2098
|
+
*
|
|
2099
|
+
* **The restore target must not carry pending events.** Events recorded
|
|
2100
|
+
* before the restore are unrelated to the restored stream; harvesting
|
|
2101
|
+
* them after `markRestored` would emit them with a version baseline
|
|
2102
|
+
* they were never part of. Such a target throws
|
|
2103
|
+
* {@link UnreplayableAggregateError} before anything moves (crash-loud
|
|
2104
|
+
* programming bug, never a `Result` `Err`). Unlike `loadFromHistory`,
|
|
2105
|
+
* a never-persisted in-memory version is fine here: the snapshot
|
|
2106
|
+
* overwrites state and version entirely instead of adding to them.
|
|
1632
2107
|
*/
|
|
1633
2108
|
restoreFromSnapshotWithEvents(snapshot, eventsAfterSnapshot) {
|
|
2109
|
+
this.assertReplayTargetFresh(false);
|
|
1634
2110
|
const previousState = this._state;
|
|
1635
2111
|
const previousVersion = this.version;
|
|
1636
|
-
|
|
2112
|
+
let restored;
|
|
2113
|
+
try {
|
|
2114
|
+
restored = this.fromSnapshotState(this.resolveSnapshotState(snapshot));
|
|
2115
|
+
this.validateState(restored);
|
|
2116
|
+
} catch (e) {
|
|
2117
|
+
if (e instanceof DomainError) return err(e);
|
|
2118
|
+
throw e;
|
|
2119
|
+
}
|
|
2120
|
+
this._state = this.freezeState(restored);
|
|
1637
2121
|
this.setVersion(snapshot.version);
|
|
1638
2122
|
for (const event of eventsAfterSnapshot) {
|
|
1639
2123
|
try {
|
|
@@ -1650,6 +2134,29 @@ var EventSourcedAggregate = class extends BaseAggregate {
|
|
|
1650
2134
|
);
|
|
1651
2135
|
return ok();
|
|
1652
2136
|
}
|
|
2137
|
+
/**
|
|
2138
|
+
* Replay-target freshness guard shared by `loadFromHistory` and
|
|
2139
|
+
* `restoreFromSnapshotWithEvents`. See {@link UnreplayableAggregateError}
|
|
2140
|
+
* for the desync both conditions prevent. `checkUnpersistedVersion` is
|
|
2141
|
+
* `true` for the additive `loadFromHistory` path only: the snapshot
|
|
2142
|
+
* restore overwrites version wholesale, so a never-persisted in-memory
|
|
2143
|
+
* version is harmless there.
|
|
2144
|
+
*/
|
|
2145
|
+
assertReplayTargetFresh(checkUnpersistedVersion) {
|
|
2146
|
+
const pending = this.pendingEvents.length;
|
|
2147
|
+
if (pending > 0) {
|
|
2148
|
+
throw new UnreplayableAggregateError(
|
|
2149
|
+
String(this.id),
|
|
2150
|
+
`it carries ${pending} unflushed pending event(s) that are not part of the persisted stream`
|
|
2151
|
+
);
|
|
2152
|
+
}
|
|
2153
|
+
if (checkUnpersistedVersion && this.version > 0 && this.persistedVersion === void 0) {
|
|
2154
|
+
throw new UnreplayableAggregateError(
|
|
2155
|
+
String(this.id),
|
|
2156
|
+
`its in-memory version (${this.version}) was never persisted (persistedVersion is undefined), so additive replay would mark unpersisted history as persisted`
|
|
2157
|
+
);
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
1653
2160
|
};
|
|
1654
2161
|
|
|
1655
2162
|
// src/app/describe-thrown.ts
|
|
@@ -1687,7 +2194,10 @@ var CommandBus = class {
|
|
|
1687
2194
|
if (!handler) {
|
|
1688
2195
|
return err(
|
|
1689
2196
|
this.errorMapper(
|
|
1690
|
-
new
|
|
2197
|
+
new UnregisteredHandlerError({
|
|
2198
|
+
busKind: "command",
|
|
2199
|
+
messageType: command.type
|
|
2200
|
+
})
|
|
1691
2201
|
)
|
|
1692
2202
|
);
|
|
1693
2203
|
}
|
|
@@ -1705,6 +2215,21 @@ function abortReason(signal, fallbackMessage) {
|
|
|
1705
2215
|
}
|
|
1706
2216
|
__name(abortReason, "abortReason");
|
|
1707
2217
|
|
|
2218
|
+
// src/utils/observer.ts
|
|
2219
|
+
function reportToObserver(invoke) {
|
|
2220
|
+
let result;
|
|
2221
|
+
try {
|
|
2222
|
+
result = invoke();
|
|
2223
|
+
} catch {
|
|
2224
|
+
return;
|
|
2225
|
+
}
|
|
2226
|
+
if (result !== null && typeof result === "object" && typeof result.then === "function") {
|
|
2227
|
+
result.then(void 0, () => {
|
|
2228
|
+
});
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
__name(reportToObserver, "reportToObserver");
|
|
2232
|
+
|
|
1708
2233
|
// src/app/handler.ts
|
|
1709
2234
|
async function withCommit(deps, fn) {
|
|
1710
2235
|
if (deps.signal?.aborted) {
|
|
@@ -1763,22 +2288,67 @@ async function withCommit(deps, fn) {
|
|
|
1763
2288
|
} else {
|
|
1764
2289
|
agg.markPersisted(agg.version);
|
|
1765
2290
|
}
|
|
1766
|
-
} catch {
|
|
2291
|
+
} catch (error) {
|
|
2292
|
+
reportToObserver(() => deps.onPersistError?.(error, agg));
|
|
1767
2293
|
}
|
|
1768
2294
|
}
|
|
1769
2295
|
if (deps.bus && events.length > 0) {
|
|
1770
2296
|
try {
|
|
1771
2297
|
await deps.bus.publish(events);
|
|
1772
2298
|
} catch (error) {
|
|
1773
|
-
|
|
1774
|
-
deps.onPublishError?.(error, events);
|
|
1775
|
-
} catch {
|
|
1776
|
-
}
|
|
2299
|
+
reportToObserver(() => deps.onPublishError?.(error, events));
|
|
1777
2300
|
}
|
|
1778
2301
|
}
|
|
1779
2302
|
return result;
|
|
1780
2303
|
}
|
|
1781
2304
|
__name(withCommit, "withCommit");
|
|
2305
|
+
var QueryBus = class {
|
|
2306
|
+
static {
|
|
2307
|
+
__name(this, "QueryBus");
|
|
2308
|
+
}
|
|
2309
|
+
handlers = /* @__PURE__ */ new Map();
|
|
2310
|
+
errorMapper;
|
|
2311
|
+
constructor(...args) {
|
|
2312
|
+
this.errorMapper = args[0]?.errorMapper ?? describeThrown;
|
|
2313
|
+
}
|
|
2314
|
+
register(queryType, handler) {
|
|
2315
|
+
if (this.handlers.has(queryType)) {
|
|
2316
|
+
throw new Error(
|
|
2317
|
+
`QueryBus: a handler for query type "${queryType}" is already registered`
|
|
2318
|
+
);
|
|
2319
|
+
}
|
|
2320
|
+
this.handlers.set(queryType, (query) => handler(query));
|
|
2321
|
+
}
|
|
2322
|
+
async execute(query) {
|
|
2323
|
+
const handler = this.handlers.get(query.type);
|
|
2324
|
+
if (!handler) {
|
|
2325
|
+
return err(
|
|
2326
|
+
this.errorMapper(
|
|
2327
|
+
new UnregisteredHandlerError({
|
|
2328
|
+
busKind: "query",
|
|
2329
|
+
messageType: query.type
|
|
2330
|
+
})
|
|
2331
|
+
)
|
|
2332
|
+
);
|
|
2333
|
+
}
|
|
2334
|
+
try {
|
|
2335
|
+
const result = await handler(query);
|
|
2336
|
+
return ok(result);
|
|
2337
|
+
} catch (error) {
|
|
2338
|
+
return err(this.errorMapper(error));
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
2341
|
+
async executeUnsafe(query) {
|
|
2342
|
+
const handler = this.handlers.get(query.type);
|
|
2343
|
+
if (!handler) {
|
|
2344
|
+
throw new UnregisteredHandlerError({
|
|
2345
|
+
busKind: "query",
|
|
2346
|
+
messageType: query.type
|
|
2347
|
+
});
|
|
2348
|
+
}
|
|
2349
|
+
return await handler(query);
|
|
2350
|
+
}
|
|
2351
|
+
};
|
|
1782
2352
|
|
|
1783
2353
|
// src/repo/identity-map.ts
|
|
1784
2354
|
var IdentityMap = class {
|
|
@@ -1983,6 +2553,7 @@ var UnitOfWork = class {
|
|
|
1983
2553
|
bus: this.deps.bus,
|
|
1984
2554
|
scope: this.deps.scope,
|
|
1985
2555
|
onPublishError: this.deps.onPublishError,
|
|
2556
|
+
onPersistError: this.deps.onPersistError,
|
|
1986
2557
|
signal: options?.signal
|
|
1987
2558
|
},
|
|
1988
2559
|
async (tx) => {
|
|
@@ -2010,20 +2581,11 @@ var UnitOfWork = class {
|
|
|
2010
2581
|
}
|
|
2011
2582
|
);
|
|
2012
2583
|
} catch (error) {
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
}
|
|
2019
|
-
if (workCompleted) {
|
|
2020
|
-
const harvestError = findHarvestErrorInChain(error);
|
|
2021
|
-
if (harvestError) {
|
|
2022
|
-
throw harvestError;
|
|
2023
|
-
}
|
|
2024
|
-
throw new CommitError(error);
|
|
2025
|
-
}
|
|
2026
|
-
throw error;
|
|
2584
|
+
throw classifyRunError(error, {
|
|
2585
|
+
workThrew,
|
|
2586
|
+
workCompleted,
|
|
2587
|
+
workError
|
|
2588
|
+
});
|
|
2027
2589
|
} finally {
|
|
2028
2590
|
session?.close();
|
|
2029
2591
|
this._active = false;
|
|
@@ -2121,6 +2683,23 @@ function makeContext(repositories, transaction, session, signal) {
|
|
|
2121
2683
|
};
|
|
2122
2684
|
}
|
|
2123
2685
|
__name(makeContext, "makeContext");
|
|
2686
|
+
function classifyRunError(error, state) {
|
|
2687
|
+
if (state.workThrew) {
|
|
2688
|
+
if (error === state.workError || causeChainContains(error, state.workError)) {
|
|
2689
|
+
return error;
|
|
2690
|
+
}
|
|
2691
|
+
return new RollbackError(state.workError, error);
|
|
2692
|
+
}
|
|
2693
|
+
if (state.workCompleted) {
|
|
2694
|
+
const harvestError = findHarvestErrorInChain(error);
|
|
2695
|
+
if (harvestError) {
|
|
2696
|
+
return harvestError;
|
|
2697
|
+
}
|
|
2698
|
+
return new CommitError(error);
|
|
2699
|
+
}
|
|
2700
|
+
return error;
|
|
2701
|
+
}
|
|
2702
|
+
__name(classifyRunError, "classifyRunError");
|
|
2124
2703
|
function findHarvestErrorInChain(error) {
|
|
2125
2704
|
const seen = /* @__PURE__ */ new Set();
|
|
2126
2705
|
let current = error;
|
|
@@ -2162,82 +2741,1057 @@ function causeChainContains(error, target) {
|
|
|
2162
2741
|
return false;
|
|
2163
2742
|
}
|
|
2164
2743
|
__name(causeChainContains, "causeChainContains");
|
|
2165
|
-
var
|
|
2744
|
+
var InvalidDomainTransitionError = class extends DomainError {
|
|
2745
|
+
constructor(state, inputType) {
|
|
2746
|
+
super(`No domain transition from "${state}" on "${inputType}".`);
|
|
2747
|
+
this.state = state;
|
|
2748
|
+
this.inputType = inputType;
|
|
2749
|
+
}
|
|
2166
2750
|
static {
|
|
2167
|
-
__name(this, "
|
|
2751
|
+
__name(this, "InvalidDomainTransitionError");
|
|
2168
2752
|
}
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
constructor(
|
|
2172
|
-
|
|
2753
|
+
};
|
|
2754
|
+
var DomainTransitionGuardRejectedError = class extends DomainError {
|
|
2755
|
+
constructor(state, inputType) {
|
|
2756
|
+
super(`Domain transition guard rejected "${inputType}" from "${state}".`);
|
|
2757
|
+
this.state = state;
|
|
2758
|
+
this.inputType = inputType;
|
|
2173
2759
|
}
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
throw new Error(
|
|
2177
|
-
`QueryBus: a handler for query type "${queryType}" is already registered`
|
|
2178
|
-
);
|
|
2179
|
-
}
|
|
2180
|
-
this.handlers.set(queryType, (query) => handler(query));
|
|
2760
|
+
static {
|
|
2761
|
+
__name(this, "DomainTransitionGuardRejectedError");
|
|
2181
2762
|
}
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
this.errorMapper(
|
|
2187
|
-
new Error(`No handler registered for query type: ${query.type}`)
|
|
2188
|
-
)
|
|
2189
|
-
);
|
|
2190
|
-
}
|
|
2191
|
-
try {
|
|
2192
|
-
const result = await handler(query);
|
|
2193
|
-
return ok(result);
|
|
2194
|
-
} catch (error) {
|
|
2195
|
-
return err(this.errorMapper(error));
|
|
2196
|
-
}
|
|
2763
|
+
};
|
|
2764
|
+
var InvalidDomainMachineDefinitionError = class extends BaseError {
|
|
2765
|
+
static {
|
|
2766
|
+
__name(this, "InvalidDomainMachineDefinitionError");
|
|
2197
2767
|
}
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
if (!handler) {
|
|
2201
|
-
throw new Error(`No handler registered for query type: ${query.type}`);
|
|
2202
|
-
}
|
|
2203
|
-
return await handler(query);
|
|
2768
|
+
constructor(message, cause) {
|
|
2769
|
+
super(message, cause, { name: "InvalidDomainMachineDefinitionError" });
|
|
2204
2770
|
}
|
|
2205
2771
|
};
|
|
2206
|
-
|
|
2207
|
-
// src/events/event-bus.ts
|
|
2208
|
-
var EventBusImpl = class {
|
|
2772
|
+
var InvalidDomainMachineContextError = class extends BaseError {
|
|
2209
2773
|
static {
|
|
2210
|
-
__name(this, "
|
|
2774
|
+
__name(this, "InvalidDomainMachineContextError");
|
|
2211
2775
|
}
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
const type = eventType;
|
|
2215
|
-
if (!this.handlers.has(type)) {
|
|
2216
|
-
this.handlers.set(type, []);
|
|
2217
|
-
}
|
|
2218
|
-
const handlersForType = this.handlers.get(type);
|
|
2219
|
-
const casted = handler;
|
|
2220
|
-
handlersForType.push(casted);
|
|
2221
|
-
let removed = false;
|
|
2222
|
-
return () => {
|
|
2223
|
-
if (removed) return;
|
|
2224
|
-
const idx = handlersForType.indexOf(casted);
|
|
2225
|
-
if (idx !== -1) {
|
|
2226
|
-
handlersForType.splice(idx, 1);
|
|
2227
|
-
removed = true;
|
|
2228
|
-
}
|
|
2229
|
-
if (handlersForType.length === 0) {
|
|
2230
|
-
this.handlers.delete(type);
|
|
2231
|
-
}
|
|
2232
|
-
};
|
|
2776
|
+
constructor(message, cause) {
|
|
2777
|
+
super(message, cause, { name: "InvalidDomainMachineContextError" });
|
|
2233
2778
|
}
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2779
|
+
};
|
|
2780
|
+
var InvalidDomainMachineSnapshotError = class extends BaseError {
|
|
2781
|
+
static {
|
|
2782
|
+
__name(this, "InvalidDomainMachineSnapshotError");
|
|
2783
|
+
}
|
|
2784
|
+
constructor(message, cause) {
|
|
2785
|
+
super(message, cause, { name: "InvalidDomainMachineSnapshotError" });
|
|
2786
|
+
}
|
|
2787
|
+
};
|
|
2788
|
+
var InvalidDomainMachineInputError = class extends BaseError {
|
|
2789
|
+
static {
|
|
2790
|
+
__name(this, "InvalidDomainMachineInputError");
|
|
2791
|
+
}
|
|
2792
|
+
constructor(message, cause) {
|
|
2793
|
+
super(message, cause, { name: "InvalidDomainMachineInputError" });
|
|
2794
|
+
}
|
|
2795
|
+
};
|
|
2796
|
+
var InvalidDomainTransitionGuardResultError = class extends BaseError {
|
|
2797
|
+
static {
|
|
2798
|
+
__name(this, "InvalidDomainTransitionGuardResultError");
|
|
2799
|
+
}
|
|
2800
|
+
constructor(message, cause) {
|
|
2801
|
+
super(message, cause, { name: "InvalidDomainTransitionGuardResultError" });
|
|
2802
|
+
}
|
|
2803
|
+
};
|
|
2804
|
+
var InvalidDomainTransitionResultError = class extends BaseError {
|
|
2805
|
+
static {
|
|
2806
|
+
__name(this, "InvalidDomainTransitionResultError");
|
|
2807
|
+
}
|
|
2808
|
+
constructor(message, cause) {
|
|
2809
|
+
super(message, cause, { name: "InvalidDomainTransitionResultError" });
|
|
2810
|
+
}
|
|
2811
|
+
};
|
|
2812
|
+
var ReentrantDomainStateMachineEvaluationError = class extends BaseError {
|
|
2813
|
+
static {
|
|
2814
|
+
__name(this, "ReentrantDomainStateMachineEvaluationError");
|
|
2815
|
+
}
|
|
2816
|
+
constructor() {
|
|
2817
|
+
super(
|
|
2818
|
+
"Domain state machine callbacks cannot evaluate the same machine.",
|
|
2819
|
+
void 0,
|
|
2820
|
+
{ name: "ReentrantDomainStateMachineEvaluationError" }
|
|
2821
|
+
);
|
|
2822
|
+
}
|
|
2823
|
+
};
|
|
2824
|
+
|
|
2825
|
+
// src/domain-state-machine/machine-data.ts
|
|
2826
|
+
var DOMAIN_MACHINE_DATA_MAX_DEPTH = 256;
|
|
2827
|
+
var DOMAIN_MACHINE_DATA_MAX_NODES = 1e4;
|
|
2828
|
+
var DOMAIN_MACHINE_DATA_MAX_PROPERTIES = 1e5;
|
|
2829
|
+
function copyDomainMachineOutputs(outputs) {
|
|
2830
|
+
try {
|
|
2831
|
+
const copiedOutputs = cloneDomainMachineDataValue(
|
|
2832
|
+
outputs ?? [],
|
|
2833
|
+
createDomainTransitionOutputError
|
|
2834
|
+
);
|
|
2835
|
+
return deepFreeze(
|
|
2836
|
+
copiedOutputs
|
|
2837
|
+
);
|
|
2838
|
+
} catch (cause) {
|
|
2839
|
+
if (cause instanceof InvalidDomainTransitionResultError) {
|
|
2840
|
+
throw cause;
|
|
2841
|
+
}
|
|
2842
|
+
throw new InvalidDomainTransitionResultError(
|
|
2843
|
+
"Domain transition result outputs must contain cloneable, deeply immutable data.",
|
|
2844
|
+
cause
|
|
2845
|
+
);
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
__name(copyDomainMachineOutputs, "copyDomainMachineOutputs");
|
|
2849
|
+
function copyDomainMachineInput(input) {
|
|
2850
|
+
try {
|
|
2851
|
+
return deepFreeze(
|
|
2852
|
+
cloneDomainMachineDataValue(input, createDomainMachineInputError)
|
|
2853
|
+
);
|
|
2854
|
+
} catch (cause) {
|
|
2855
|
+
if (cause instanceof InvalidDomainMachineInputError) {
|
|
2856
|
+
throw cause;
|
|
2857
|
+
}
|
|
2858
|
+
throw new InvalidDomainMachineInputError(
|
|
2859
|
+
"Domain machine input must contain cloneable, deeply immutable data.",
|
|
2860
|
+
cause
|
|
2861
|
+
);
|
|
2862
|
+
}
|
|
2863
|
+
}
|
|
2864
|
+
__name(copyDomainMachineInput, "copyDomainMachineInput");
|
|
2865
|
+
function copyDomainMachineContext(context) {
|
|
2866
|
+
try {
|
|
2867
|
+
return deepFreeze(
|
|
2868
|
+
cloneDomainMachineDataValue(context, createDomainMachineContextError)
|
|
2869
|
+
);
|
|
2870
|
+
} catch (cause) {
|
|
2871
|
+
if (cause instanceof InvalidDomainMachineContextError) {
|
|
2872
|
+
throw cause;
|
|
2873
|
+
}
|
|
2874
|
+
throw new InvalidDomainMachineContextError(
|
|
2875
|
+
"Domain machine context must contain cloneable, deeply immutable data.",
|
|
2876
|
+
cause
|
|
2877
|
+
);
|
|
2878
|
+
}
|
|
2879
|
+
}
|
|
2880
|
+
__name(copyDomainMachineContext, "copyDomainMachineContext");
|
|
2881
|
+
function createDomainMachineContextError(message, cause) {
|
|
2882
|
+
return new InvalidDomainMachineContextError(message, cause);
|
|
2883
|
+
}
|
|
2884
|
+
__name(createDomainMachineContextError, "createDomainMachineContextError");
|
|
2885
|
+
function createDomainMachineInputError(message, cause) {
|
|
2886
|
+
return new InvalidDomainMachineInputError(message, cause);
|
|
2887
|
+
}
|
|
2888
|
+
__name(createDomainMachineInputError, "createDomainMachineInputError");
|
|
2889
|
+
function createDomainTransitionOutputError(message, cause) {
|
|
2890
|
+
return new InvalidDomainTransitionResultError(message, cause);
|
|
2891
|
+
}
|
|
2892
|
+
__name(createDomainTransitionOutputError, "createDomainTransitionOutputError");
|
|
2893
|
+
function cloneDomainMachineDataValue(value, errorFactory, seen = /* @__PURE__ */ new WeakMap(), traversal = { nodes: 0, properties: 0 }, depth = 0) {
|
|
2894
|
+
if (typeof value === "function") {
|
|
2895
|
+
throw errorFactory("Domain machine data cannot contain function values.");
|
|
2896
|
+
}
|
|
2897
|
+
if (value === null || typeof value !== "object") return value;
|
|
2898
|
+
const source = value;
|
|
2899
|
+
if (depth > DOMAIN_MACHINE_DATA_MAX_DEPTH) {
|
|
2900
|
+
throw errorFactory(
|
|
2901
|
+
`Domain machine data exceeds the maximum depth of ${DOMAIN_MACHINE_DATA_MAX_DEPTH}.`
|
|
2902
|
+
);
|
|
2903
|
+
}
|
|
2904
|
+
const existing = seen.get(source);
|
|
2905
|
+
if (existing !== void 0) return existing;
|
|
2906
|
+
traversal.nodes += 1;
|
|
2907
|
+
if (traversal.nodes > DOMAIN_MACHINE_DATA_MAX_NODES) {
|
|
2908
|
+
throw errorFactory(
|
|
2909
|
+
`Domain machine data contains more than ${DOMAIN_MACHINE_DATA_MAX_NODES.toLocaleString("en-US")} object nodes.`
|
|
2910
|
+
);
|
|
2911
|
+
}
|
|
2912
|
+
const toStringTagDescriptor = findPropertyDescriptor(
|
|
2913
|
+
source,
|
|
2914
|
+
Symbol.toStringTag
|
|
2915
|
+
);
|
|
2916
|
+
if (toStringTagDescriptor !== void 0 && !("value" in toStringTagDescriptor)) {
|
|
2917
|
+
throw errorFactory(
|
|
2918
|
+
"Domain machine data cannot contain accessor properties."
|
|
2919
|
+
);
|
|
2920
|
+
}
|
|
2921
|
+
if (Array.isArray(value)) {
|
|
2922
|
+
if (!isIntrinsicArrayPrototype(Object.getPrototypeOf(value))) {
|
|
2923
|
+
throw errorFactory(
|
|
2924
|
+
"Domain machine data cannot contain custom Array instances."
|
|
2925
|
+
);
|
|
2926
|
+
}
|
|
2927
|
+
const cloned2 = new Array(value.length);
|
|
2928
|
+
seen.set(source, cloned2);
|
|
2929
|
+
for (const key of readDomainMachineDataKeys(
|
|
2930
|
+
source,
|
|
2931
|
+
errorFactory,
|
|
2932
|
+
traversal
|
|
2933
|
+
)) {
|
|
2934
|
+
const descriptor = Object.getOwnPropertyDescriptor(source, key);
|
|
2935
|
+
if (!descriptor) continue;
|
|
2936
|
+
if (!("value" in descriptor)) {
|
|
2937
|
+
throw errorFactory(
|
|
2938
|
+
"Domain machine data cannot contain accessor properties."
|
|
2939
|
+
);
|
|
2940
|
+
}
|
|
2941
|
+
if (key === "length") continue;
|
|
2942
|
+
descriptor.value = cloneDomainMachineDataValue(
|
|
2943
|
+
descriptor.value,
|
|
2944
|
+
errorFactory,
|
|
2945
|
+
seen,
|
|
2946
|
+
traversal,
|
|
2947
|
+
depth + 1
|
|
2948
|
+
);
|
|
2949
|
+
Object.defineProperty(cloned2, key, descriptor);
|
|
2950
|
+
}
|
|
2951
|
+
return cloned2;
|
|
2952
|
+
}
|
|
2953
|
+
const prototype = Object.getPrototypeOf(source);
|
|
2954
|
+
if (prototype !== null && !isIntrinsicObjectPrototype(prototype)) {
|
|
2955
|
+
throw errorFactory(
|
|
2956
|
+
"Domain machine data cannot contain custom class instances."
|
|
2957
|
+
);
|
|
2958
|
+
}
|
|
2959
|
+
const tag = Object.prototype.toString.call(source);
|
|
2960
|
+
if (isBuiltInObject(source, tag) || ArrayBuffer.isView(source)) {
|
|
2961
|
+
throw errorFactory(
|
|
2962
|
+
`Domain machine data cannot contain ${tag.slice(8, -1)} object values.`
|
|
2963
|
+
);
|
|
2964
|
+
}
|
|
2965
|
+
const cloned = Object.create(prototype === null ? null : Object.prototype);
|
|
2966
|
+
seen.set(source, cloned);
|
|
2967
|
+
for (const key of readDomainMachineDataKeys(
|
|
2968
|
+
source,
|
|
2969
|
+
errorFactory,
|
|
2970
|
+
traversal
|
|
2971
|
+
)) {
|
|
2972
|
+
const descriptor = Object.getOwnPropertyDescriptor(source, key);
|
|
2973
|
+
if (!descriptor) continue;
|
|
2974
|
+
if (!("value" in descriptor)) {
|
|
2975
|
+
throw errorFactory(
|
|
2976
|
+
"Domain machine data cannot contain accessor properties."
|
|
2977
|
+
);
|
|
2978
|
+
}
|
|
2979
|
+
descriptor.value = cloneDomainMachineDataValue(
|
|
2980
|
+
descriptor.value,
|
|
2981
|
+
errorFactory,
|
|
2982
|
+
seen,
|
|
2983
|
+
traversal,
|
|
2984
|
+
depth + 1
|
|
2985
|
+
);
|
|
2986
|
+
Object.defineProperty(cloned, key, descriptor);
|
|
2987
|
+
}
|
|
2988
|
+
return cloned;
|
|
2989
|
+
}
|
|
2990
|
+
__name(cloneDomainMachineDataValue, "cloneDomainMachineDataValue");
|
|
2991
|
+
function readDomainMachineDataKeys(value, errorFactory, traversal) {
|
|
2992
|
+
const keys = Reflect.ownKeys(value);
|
|
2993
|
+
traversal.properties += keys.length;
|
|
2994
|
+
if (traversal.properties > DOMAIN_MACHINE_DATA_MAX_PROPERTIES) {
|
|
2995
|
+
throw errorFactory(
|
|
2996
|
+
`Domain machine data contains more than ${DOMAIN_MACHINE_DATA_MAX_PROPERTIES.toLocaleString("en-US")} own properties.`
|
|
2997
|
+
);
|
|
2998
|
+
}
|
|
2999
|
+
return keys;
|
|
3000
|
+
}
|
|
3001
|
+
__name(readDomainMachineDataKeys, "readDomainMachineDataKeys");
|
|
3002
|
+
function isRecord(value) {
|
|
3003
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3004
|
+
}
|
|
3005
|
+
__name(isRecord, "isRecord");
|
|
3006
|
+
function isPlainRecord(value) {
|
|
3007
|
+
if (!isRecord(value)) return false;
|
|
3008
|
+
const prototype = Object.getPrototypeOf(value);
|
|
3009
|
+
return prototype === null || isIntrinsicObjectPrototype(prototype);
|
|
3010
|
+
}
|
|
3011
|
+
__name(isPlainRecord, "isPlainRecord");
|
|
3012
|
+
function isIntrinsicArrayPrototype(prototype) {
|
|
3013
|
+
if (prototype === null || !Array.isArray(prototype)) return false;
|
|
3014
|
+
if (!isIntrinsicConstructorPrototype(prototype, "Array")) return false;
|
|
3015
|
+
const parentPrototype = Object.getPrototypeOf(prototype);
|
|
3016
|
+
return parentPrototype !== null && isIntrinsicObjectPrototype(parentPrototype);
|
|
3017
|
+
}
|
|
3018
|
+
__name(isIntrinsicArrayPrototype, "isIntrinsicArrayPrototype");
|
|
3019
|
+
function isIntrinsicObjectPrototype(prototype) {
|
|
3020
|
+
return Object.getPrototypeOf(prototype) === null && isIntrinsicConstructorPrototype(prototype, "Object");
|
|
3021
|
+
}
|
|
3022
|
+
__name(isIntrinsicObjectPrototype, "isIntrinsicObjectPrototype");
|
|
3023
|
+
function hasOwn(value, key) {
|
|
3024
|
+
return Object.hasOwn(value, key);
|
|
3025
|
+
}
|
|
3026
|
+
__name(hasOwn, "hasOwn");
|
|
3027
|
+
|
|
3028
|
+
// src/domain-state-machine/definition.ts
|
|
3029
|
+
var DOMAIN_MACHINE_DEFINITION_KEYS = /* @__PURE__ */ new Set([
|
|
3030
|
+
"initial",
|
|
3031
|
+
"initialContext",
|
|
3032
|
+
"validateSnapshot",
|
|
3033
|
+
"states"
|
|
3034
|
+
]);
|
|
3035
|
+
var DOMAIN_MACHINE_STATE_NODE_KEYS = /* @__PURE__ */ new Set([
|
|
3036
|
+
"terminal",
|
|
3037
|
+
"validateContext",
|
|
3038
|
+
"on"
|
|
3039
|
+
]);
|
|
3040
|
+
var DOMAIN_MACHINE_TRANSITION_KEYS = /* @__PURE__ */ new Set([
|
|
3041
|
+
"target",
|
|
3042
|
+
"guard",
|
|
3043
|
+
"reduce"
|
|
3044
|
+
]);
|
|
3045
|
+
function copyDomainMachineDefinition(definition) {
|
|
3046
|
+
const states = readDomainMachineDefinitionProperty(
|
|
3047
|
+
definition,
|
|
3048
|
+
"states"
|
|
3049
|
+
);
|
|
3050
|
+
const copiedStates = /* @__PURE__ */ Object.create(null);
|
|
3051
|
+
for (const state of Object.keys(states)) {
|
|
3052
|
+
const node = readDomainMachineDefinitionProperty(
|
|
3053
|
+
states,
|
|
3054
|
+
state
|
|
3055
|
+
);
|
|
3056
|
+
const transitions = readOptionalDomainMachineDefinitionProperty(
|
|
3057
|
+
node,
|
|
3058
|
+
"on"
|
|
3059
|
+
);
|
|
3060
|
+
const copiedTransitions = /* @__PURE__ */ Object.create(null);
|
|
3061
|
+
for (const inputType of Object.keys(
|
|
3062
|
+
transitions ?? {}
|
|
3063
|
+
)) {
|
|
3064
|
+
const transition = readDomainMachineDefinitionProperty(
|
|
3065
|
+
transitions,
|
|
3066
|
+
inputType
|
|
3067
|
+
);
|
|
3068
|
+
if (transition) {
|
|
3069
|
+
const copiedTransition = Object.freeze({
|
|
3070
|
+
target: readDomainMachineDefinitionProperty(transition, "target"),
|
|
3071
|
+
guard: readOptionalDomainMachineDefinitionProperty(
|
|
3072
|
+
transition,
|
|
3073
|
+
"guard"
|
|
3074
|
+
),
|
|
3075
|
+
reduce: readOptionalDomainMachineDefinitionProperty(
|
|
3076
|
+
transition,
|
|
3077
|
+
"reduce"
|
|
3078
|
+
)
|
|
3079
|
+
});
|
|
3080
|
+
Object.defineProperty(copiedTransitions, inputType, {
|
|
3081
|
+
value: copiedTransition,
|
|
3082
|
+
enumerable: true
|
|
3083
|
+
});
|
|
3084
|
+
}
|
|
3085
|
+
}
|
|
3086
|
+
Object.defineProperty(copiedStates, state, {
|
|
3087
|
+
value: Object.freeze({
|
|
3088
|
+
terminal: readOptionalDomainMachineDefinitionProperty(node, "terminal"),
|
|
3089
|
+
validateContext: readOptionalDomainMachineDefinitionProperty(
|
|
3090
|
+
node,
|
|
3091
|
+
"validateContext"
|
|
3092
|
+
),
|
|
3093
|
+
on: Object.freeze(copiedTransitions)
|
|
3094
|
+
}),
|
|
3095
|
+
enumerable: true
|
|
3096
|
+
});
|
|
3097
|
+
}
|
|
3098
|
+
return Object.freeze({
|
|
3099
|
+
initial: readDomainMachineDefinitionProperty(
|
|
3100
|
+
definition,
|
|
3101
|
+
"initial"
|
|
3102
|
+
),
|
|
3103
|
+
initialContext: readDomainMachineDefinitionProperty(
|
|
3104
|
+
definition,
|
|
3105
|
+
"initialContext"
|
|
3106
|
+
),
|
|
3107
|
+
validateSnapshot: readOptionalDomainMachineDefinitionProperty(
|
|
3108
|
+
definition,
|
|
3109
|
+
"validateSnapshot"
|
|
3110
|
+
),
|
|
3111
|
+
states: Object.freeze(copiedStates)
|
|
3112
|
+
});
|
|
3113
|
+
}
|
|
3114
|
+
__name(copyDomainMachineDefinition, "copyDomainMachineDefinition");
|
|
3115
|
+
var preparedDefinitions = /* @__PURE__ */ new WeakSet();
|
|
3116
|
+
function ensureStableDomainMachineDefinition(definition) {
|
|
3117
|
+
if (preparedDefinitions.has(definition)) return definition;
|
|
3118
|
+
validateDomainMachineDefinition(definition);
|
|
3119
|
+
return copyDomainMachineDefinition(definition);
|
|
3120
|
+
}
|
|
3121
|
+
__name(ensureStableDomainMachineDefinition, "ensureStableDomainMachineDefinition");
|
|
3122
|
+
function prepareDomainMachineDefinition(definition) {
|
|
3123
|
+
const stable = ensureStableDomainMachineDefinition(definition);
|
|
3124
|
+
preparedDefinitions.add(stable);
|
|
3125
|
+
return stable;
|
|
3126
|
+
}
|
|
3127
|
+
__name(prepareDomainMachineDefinition, "prepareDomainMachineDefinition");
|
|
3128
|
+
function getTransition(definition, state, input) {
|
|
3129
|
+
const transitions = definition.states[state].on;
|
|
3130
|
+
if (!transitions || !hasOwn(transitions, input.type)) return void 0;
|
|
3131
|
+
return transitions[input.type];
|
|
3132
|
+
}
|
|
3133
|
+
__name(getTransition, "getTransition");
|
|
3134
|
+
function validateDomainMachineDefinition(definition) {
|
|
3135
|
+
const candidate = definition;
|
|
3136
|
+
if (!isPlainRecord(candidate)) {
|
|
3137
|
+
throw new InvalidDomainMachineDefinitionError(
|
|
3138
|
+
"Domain machine definition must be a plain object."
|
|
3139
|
+
);
|
|
3140
|
+
}
|
|
3141
|
+
assertDomainMachineDefinitionDataProperties(
|
|
3142
|
+
candidate,
|
|
3143
|
+
DOMAIN_MACHINE_DEFINITION_KEYS
|
|
3144
|
+
);
|
|
3145
|
+
const initial = readDomainMachineDefinitionProperty(candidate, "initial");
|
|
3146
|
+
if (typeof initial !== "string") {
|
|
3147
|
+
throw new InvalidDomainMachineDefinitionError(
|
|
3148
|
+
"Domain machine initial state must be a string data property."
|
|
3149
|
+
);
|
|
3150
|
+
}
|
|
3151
|
+
if (typeof readDomainMachineDefinitionProperty(candidate, "initialContext") !== "function") {
|
|
3152
|
+
throw new InvalidDomainMachineDefinitionError(
|
|
3153
|
+
"Domain machine initialContext must be a function data property."
|
|
3154
|
+
);
|
|
3155
|
+
}
|
|
3156
|
+
const validateSnapshot = readOptionalDomainMachineDefinitionProperty(
|
|
3157
|
+
candidate,
|
|
3158
|
+
"validateSnapshot"
|
|
3159
|
+
);
|
|
3160
|
+
if (validateSnapshot !== void 0 && typeof validateSnapshot !== "function") {
|
|
3161
|
+
throw new InvalidDomainMachineDefinitionError(
|
|
3162
|
+
"Domain machine validateSnapshot must be a function data property."
|
|
3163
|
+
);
|
|
3164
|
+
}
|
|
3165
|
+
const statesCandidate = readDomainMachineDefinitionProperty(
|
|
3166
|
+
candidate,
|
|
3167
|
+
"states"
|
|
3168
|
+
);
|
|
3169
|
+
if (!isPlainRecord(statesCandidate)) {
|
|
3170
|
+
throw new InvalidDomainMachineDefinitionError(
|
|
3171
|
+
"Domain machine states must be a plain object data property."
|
|
3172
|
+
);
|
|
3173
|
+
}
|
|
3174
|
+
const states = statesCandidate;
|
|
3175
|
+
assertDomainMachineDefinitionEntryMap(states, "state");
|
|
3176
|
+
if (!hasOwn(states, initial)) {
|
|
3177
|
+
throw new InvalidDomainMachineDefinitionError(
|
|
3178
|
+
`Initial domain machine state "${initial}" is not defined.`
|
|
3179
|
+
);
|
|
3180
|
+
}
|
|
3181
|
+
for (const state of Object.keys(states)) {
|
|
3182
|
+
const node = readDomainMachineDefinitionProperty(states, state);
|
|
3183
|
+
if (!isPlainRecord(node)) {
|
|
3184
|
+
throw new InvalidDomainMachineDefinitionError(
|
|
3185
|
+
`Domain machine state "${state}" must be a plain object data property.`
|
|
3186
|
+
);
|
|
3187
|
+
}
|
|
3188
|
+
assertDomainMachineDefinitionDataProperties(
|
|
3189
|
+
node,
|
|
3190
|
+
DOMAIN_MACHINE_STATE_NODE_KEYS
|
|
3191
|
+
);
|
|
3192
|
+
const terminal = readOptionalDomainMachineDefinitionProperty(
|
|
3193
|
+
node,
|
|
3194
|
+
"terminal"
|
|
3195
|
+
);
|
|
3196
|
+
if (terminal !== void 0 && typeof terminal !== "boolean") {
|
|
3197
|
+
throw new InvalidDomainMachineDefinitionError(
|
|
3198
|
+
`Domain machine state "${state}" terminal flag must be a boolean.`
|
|
3199
|
+
);
|
|
3200
|
+
}
|
|
3201
|
+
const validateContext = readOptionalDomainMachineDefinitionProperty(node, "validateContext");
|
|
3202
|
+
if (validateContext !== void 0 && typeof validateContext !== "function") {
|
|
3203
|
+
throw new InvalidDomainMachineDefinitionError(
|
|
3204
|
+
`Domain machine state "${state}" validateContext must be a function.`
|
|
3205
|
+
);
|
|
3206
|
+
}
|
|
3207
|
+
const transitions = readOptionalDomainMachineDefinitionProperty(
|
|
3208
|
+
node,
|
|
3209
|
+
"on"
|
|
3210
|
+
);
|
|
3211
|
+
if (transitions !== void 0 && !isPlainRecord(transitions)) {
|
|
3212
|
+
throw new InvalidDomainMachineDefinitionError(
|
|
3213
|
+
`Domain machine state "${state}" transitions must be a plain object.`
|
|
3214
|
+
);
|
|
3215
|
+
}
|
|
3216
|
+
if (isPlainRecord(transitions)) {
|
|
3217
|
+
assertDomainMachineDefinitionEntryMap(transitions, "input");
|
|
3218
|
+
}
|
|
3219
|
+
const inputTypes = Object.keys(transitions ?? {});
|
|
3220
|
+
if (terminal === true && inputTypes.length > 0) {
|
|
3221
|
+
throw new InvalidDomainMachineDefinitionError(
|
|
3222
|
+
`Terminal domain machine state "${state}" cannot declare transitions.`
|
|
3223
|
+
);
|
|
3224
|
+
}
|
|
3225
|
+
for (const inputType of inputTypes) {
|
|
3226
|
+
const transition = readDomainMachineDefinitionProperty(
|
|
3227
|
+
transitions,
|
|
3228
|
+
inputType
|
|
3229
|
+
);
|
|
3230
|
+
if (!isPlainRecord(transition)) {
|
|
3231
|
+
throw new InvalidDomainMachineDefinitionError(
|
|
3232
|
+
`Domain transition from "${state}" on "${inputType}" must be a plain object.`
|
|
3233
|
+
);
|
|
3234
|
+
}
|
|
3235
|
+
assertDomainMachineDefinitionDataProperties(
|
|
3236
|
+
transition,
|
|
3237
|
+
DOMAIN_MACHINE_TRANSITION_KEYS
|
|
3238
|
+
);
|
|
3239
|
+
const target = readDomainMachineDefinitionProperty(
|
|
3240
|
+
transition,
|
|
3241
|
+
"target"
|
|
3242
|
+
);
|
|
3243
|
+
if (typeof target !== "string") {
|
|
3244
|
+
throw new InvalidDomainMachineDefinitionError(
|
|
3245
|
+
`Domain transition from "${state}" on "${inputType}" must target a string state.`
|
|
3246
|
+
);
|
|
3247
|
+
}
|
|
3248
|
+
if (!hasOwn(states, target)) {
|
|
3249
|
+
throw new InvalidDomainMachineDefinitionError(
|
|
3250
|
+
`Domain transition from "${state}" on "${inputType}" targets unknown state "${target}".`
|
|
3251
|
+
);
|
|
3252
|
+
}
|
|
3253
|
+
const guard = readOptionalDomainMachineDefinitionProperty(
|
|
3254
|
+
transition,
|
|
3255
|
+
"guard"
|
|
3256
|
+
);
|
|
3257
|
+
if (guard !== void 0 && typeof guard !== "function") {
|
|
3258
|
+
throw new InvalidDomainMachineDefinitionError(
|
|
3259
|
+
`Domain transition from "${state}" on "${inputType}" guard must be a function.`
|
|
3260
|
+
);
|
|
3261
|
+
}
|
|
3262
|
+
const reduce = readOptionalDomainMachineDefinitionProperty(
|
|
3263
|
+
transition,
|
|
3264
|
+
"reduce"
|
|
3265
|
+
);
|
|
3266
|
+
if (reduce !== void 0 && typeof reduce !== "function") {
|
|
3267
|
+
throw new InvalidDomainMachineDefinitionError(
|
|
3268
|
+
`Domain transition from "${state}" on "${inputType}" reduce must be a function.`
|
|
3269
|
+
);
|
|
3270
|
+
}
|
|
3271
|
+
}
|
|
3272
|
+
}
|
|
3273
|
+
}
|
|
3274
|
+
__name(validateDomainMachineDefinition, "validateDomainMachineDefinition");
|
|
3275
|
+
function assertDomainMachineDefinitionDataProperties(value, allowedKeys) {
|
|
3276
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
3277
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
3278
|
+
if (descriptor !== void 0 && !("value" in descriptor)) {
|
|
3279
|
+
throw new InvalidDomainMachineDefinitionError(
|
|
3280
|
+
"Domain machine definition must contain data properties only."
|
|
3281
|
+
);
|
|
3282
|
+
}
|
|
3283
|
+
if (allowedKeys !== void 0 && !allowedKeys.has(key)) {
|
|
3284
|
+
throw new InvalidDomainMachineDefinitionError(
|
|
3285
|
+
`Domain machine definition contains unknown property "${String(key)}".`
|
|
3286
|
+
);
|
|
3287
|
+
}
|
|
3288
|
+
}
|
|
3289
|
+
}
|
|
3290
|
+
__name(assertDomainMachineDefinitionDataProperties, "assertDomainMachineDefinitionDataProperties");
|
|
3291
|
+
function assertDomainMachineDefinitionEntryMap(value, entryName) {
|
|
3292
|
+
assertDomainMachineDefinitionDataProperties(value);
|
|
3293
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
3294
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
3295
|
+
if (typeof key !== "string" || descriptor?.enumerable !== true) {
|
|
3296
|
+
throw new InvalidDomainMachineDefinitionError(
|
|
3297
|
+
`Domain machine ${entryName} names must be enumerable string properties.`
|
|
3298
|
+
);
|
|
3299
|
+
}
|
|
3300
|
+
}
|
|
3301
|
+
}
|
|
3302
|
+
__name(assertDomainMachineDefinitionEntryMap, "assertDomainMachineDefinitionEntryMap");
|
|
3303
|
+
function readDomainMachineDefinitionProperty(value, key) {
|
|
3304
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
3305
|
+
if (descriptor === void 0 || !("value" in descriptor)) {
|
|
3306
|
+
throw new InvalidDomainMachineDefinitionError(
|
|
3307
|
+
"Domain machine definition must contain data properties only."
|
|
3308
|
+
);
|
|
3309
|
+
}
|
|
3310
|
+
return descriptor.value;
|
|
3311
|
+
}
|
|
3312
|
+
__name(readDomainMachineDefinitionProperty, "readDomainMachineDefinitionProperty");
|
|
3313
|
+
function readOptionalDomainMachineDefinitionProperty(value, key) {
|
|
3314
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
3315
|
+
if (descriptor === void 0) return void 0;
|
|
3316
|
+
if (!("value" in descriptor)) {
|
|
3317
|
+
throw new InvalidDomainMachineDefinitionError(
|
|
3318
|
+
"Domain machine definition must contain data properties only."
|
|
3319
|
+
);
|
|
3320
|
+
}
|
|
3321
|
+
return descriptor.value;
|
|
3322
|
+
}
|
|
3323
|
+
__name(readOptionalDomainMachineDefinitionProperty, "readOptionalDomainMachineDefinitionProperty");
|
|
3324
|
+
|
|
3325
|
+
// src/domain-state-machine/snapshot.ts
|
|
3326
|
+
var DOMAIN_TRANSITION_RESULT_KEYS = /* @__PURE__ */ new Set([
|
|
3327
|
+
"context",
|
|
3328
|
+
"outputs"
|
|
3329
|
+
]);
|
|
3330
|
+
function prepareDomainMachineSnapshot(definition, snapshot) {
|
|
3331
|
+
validateDomainMachineSnapshot(definition, snapshot);
|
|
3332
|
+
const preparedSnapshot = createDomainMachineSnapshot(
|
|
3333
|
+
snapshot
|
|
3334
|
+
);
|
|
3335
|
+
validateDomainMachineSnapshotInvariant(definition, preparedSnapshot);
|
|
3336
|
+
return preparedSnapshot;
|
|
3337
|
+
}
|
|
3338
|
+
__name(prepareDomainMachineSnapshot, "prepareDomainMachineSnapshot");
|
|
3339
|
+
function createDomainMachineSnapshotFromPreparedContext(state, context) {
|
|
3340
|
+
return Object.freeze({ state, context });
|
|
3341
|
+
}
|
|
3342
|
+
__name(createDomainMachineSnapshotFromPreparedContext, "createDomainMachineSnapshotFromPreparedContext");
|
|
3343
|
+
function createDomainMachineSnapshot(snapshot) {
|
|
3344
|
+
return Object.freeze({
|
|
3345
|
+
state: readDomainMachineSnapshotState(snapshot),
|
|
3346
|
+
context: copyDomainMachineContext(
|
|
3347
|
+
readDomainMachineSnapshotContext(snapshot)
|
|
3348
|
+
)
|
|
3349
|
+
});
|
|
3350
|
+
}
|
|
3351
|
+
__name(createDomainMachineSnapshot, "createDomainMachineSnapshot");
|
|
3352
|
+
function validateDomainMachineSnapshot(definition, snapshot) {
|
|
3353
|
+
if (!isRecord(snapshot)) {
|
|
3354
|
+
throw new InvalidDomainMachineSnapshotError(
|
|
3355
|
+
"Domain machine snapshot must be an object."
|
|
3356
|
+
);
|
|
3357
|
+
}
|
|
3358
|
+
const state = readDomainMachineSnapshotState(snapshot);
|
|
3359
|
+
readDomainMachineSnapshotContext(snapshot);
|
|
3360
|
+
if (!hasOwn(definition.states, state)) {
|
|
3361
|
+
throw new InvalidDomainMachineSnapshotError(
|
|
3362
|
+
`Domain machine snapshot state "${state}" is not defined.`
|
|
3363
|
+
);
|
|
3364
|
+
}
|
|
3365
|
+
}
|
|
3366
|
+
__name(validateDomainMachineSnapshot, "validateDomainMachineSnapshot");
|
|
3367
|
+
function validateDomainMachineSnapshotInvariant(definition, snapshot) {
|
|
3368
|
+
const stateNode = definition.states[snapshot.state];
|
|
3369
|
+
if (stateNode.validateContext !== void 0) {
|
|
3370
|
+
const validContext = stateNode.validateContext({
|
|
3371
|
+
state: snapshot.state,
|
|
3372
|
+
context: snapshot.context
|
|
3373
|
+
});
|
|
3374
|
+
if (typeof validContext !== "boolean") {
|
|
3375
|
+
throw new InvalidDomainMachineDefinitionError(
|
|
3376
|
+
`Domain machine state "${snapshot.state}" validateContext must return a boolean.`
|
|
3377
|
+
);
|
|
3378
|
+
}
|
|
3379
|
+
if (!validContext) {
|
|
3380
|
+
throw new InvalidDomainMachineSnapshotError(
|
|
3381
|
+
`Domain machine snapshot violates the context invariant for state "${snapshot.state}".`
|
|
3382
|
+
);
|
|
3383
|
+
}
|
|
3384
|
+
}
|
|
3385
|
+
if (definition.validateSnapshot === void 0) return;
|
|
3386
|
+
const valid = definition.validateSnapshot(snapshot);
|
|
3387
|
+
if (typeof valid !== "boolean") {
|
|
3388
|
+
throw new InvalidDomainMachineDefinitionError(
|
|
3389
|
+
"Domain machine validateSnapshot must return a boolean."
|
|
3390
|
+
);
|
|
3391
|
+
}
|
|
3392
|
+
if (!valid) {
|
|
3393
|
+
throw new InvalidDomainMachineSnapshotError(
|
|
3394
|
+
`Domain machine snapshot violates invariants for state "${snapshot.state}".`
|
|
3395
|
+
);
|
|
3396
|
+
}
|
|
3397
|
+
}
|
|
3398
|
+
__name(validateDomainMachineSnapshotInvariant, "validateDomainMachineSnapshotInvariant");
|
|
3399
|
+
function readDomainMachineSnapshotState(snapshot) {
|
|
3400
|
+
const stateDescriptor = Object.getOwnPropertyDescriptor(snapshot, "state");
|
|
3401
|
+
if (stateDescriptor === void 0 || !("value" in stateDescriptor) || typeof stateDescriptor.value !== "string") {
|
|
3402
|
+
throw new InvalidDomainMachineSnapshotError(
|
|
3403
|
+
"Domain machine snapshot state must be a string data property."
|
|
3404
|
+
);
|
|
3405
|
+
}
|
|
3406
|
+
return stateDescriptor.value;
|
|
3407
|
+
}
|
|
3408
|
+
__name(readDomainMachineSnapshotState, "readDomainMachineSnapshotState");
|
|
3409
|
+
function readDomainMachineSnapshotContext(snapshot) {
|
|
3410
|
+
const contextDescriptor = Object.getOwnPropertyDescriptor(
|
|
3411
|
+
snapshot,
|
|
3412
|
+
"context"
|
|
3413
|
+
);
|
|
3414
|
+
if (contextDescriptor === void 0 || !("value" in contextDescriptor)) {
|
|
3415
|
+
throw new InvalidDomainMachineSnapshotError(
|
|
3416
|
+
"Domain machine snapshot context must be present as a data property."
|
|
3417
|
+
);
|
|
3418
|
+
}
|
|
3419
|
+
return contextDescriptor.value;
|
|
3420
|
+
}
|
|
3421
|
+
__name(readDomainMachineSnapshotContext, "readDomainMachineSnapshotContext");
|
|
3422
|
+
function validateDomainMachineInput(input) {
|
|
3423
|
+
if (!isDomainMachineInput(input)) {
|
|
3424
|
+
throw new InvalidDomainMachineInputError(
|
|
3425
|
+
"Domain machine input must be an object with a string type."
|
|
3426
|
+
);
|
|
3427
|
+
}
|
|
3428
|
+
}
|
|
3429
|
+
__name(validateDomainMachineInput, "validateDomainMachineInput");
|
|
3430
|
+
function isDomainMachineInput(input) {
|
|
3431
|
+
if (!isRecord(input)) return false;
|
|
3432
|
+
const typeDescriptor = Object.getOwnPropertyDescriptor(input, "type");
|
|
3433
|
+
return typeDescriptor !== void 0 && "value" in typeDescriptor && typeof typeDescriptor.value === "string";
|
|
3434
|
+
}
|
|
3435
|
+
__name(isDomainMachineInput, "isDomainMachineInput");
|
|
3436
|
+
function resolveDomainTransitionGuardResult(result) {
|
|
3437
|
+
if (typeof result === "boolean") return { allowed: result };
|
|
3438
|
+
if (result instanceof DomainError) {
|
|
3439
|
+
return { allowed: false, rejection: result };
|
|
3440
|
+
}
|
|
3441
|
+
throw new InvalidDomainTransitionGuardResultError(
|
|
3442
|
+
"Domain transition guard must return a boolean or DomainError."
|
|
3443
|
+
);
|
|
3444
|
+
}
|
|
3445
|
+
__name(resolveDomainTransitionGuardResult, "resolveDomainTransitionGuardResult");
|
|
3446
|
+
function validateDomainTransitionResult(result) {
|
|
3447
|
+
if (result === void 0) return;
|
|
3448
|
+
if (!isPlainRecord(result)) {
|
|
3449
|
+
throw new InvalidDomainTransitionResultError(
|
|
3450
|
+
"Domain transition result must be a plain object when returned."
|
|
3451
|
+
);
|
|
3452
|
+
}
|
|
3453
|
+
for (const key of Reflect.ownKeys(result)) {
|
|
3454
|
+
if (!DOMAIN_TRANSITION_RESULT_KEYS.has(key)) {
|
|
3455
|
+
throw new InvalidDomainTransitionResultError(
|
|
3456
|
+
`Domain transition result contains unknown property "${String(key)}".`
|
|
3457
|
+
);
|
|
3458
|
+
}
|
|
3459
|
+
const descriptor = Object.getOwnPropertyDescriptor(result, key);
|
|
3460
|
+
if (descriptor !== void 0 && !("value" in descriptor)) {
|
|
3461
|
+
throw new InvalidDomainTransitionResultError(
|
|
3462
|
+
"Domain transition result must contain data properties only."
|
|
3463
|
+
);
|
|
3464
|
+
}
|
|
3465
|
+
}
|
|
3466
|
+
const outputs = readDomainTransitionResultOutputs(result);
|
|
3467
|
+
if (outputs !== void 0 && !Array.isArray(outputs)) {
|
|
3468
|
+
throw new InvalidDomainTransitionResultError(
|
|
3469
|
+
"Domain transition result outputs must be an array when provided."
|
|
3470
|
+
);
|
|
3471
|
+
}
|
|
3472
|
+
}
|
|
3473
|
+
__name(validateDomainTransitionResult, "validateDomainTransitionResult");
|
|
3474
|
+
function readDomainTransitionResultContext(result) {
|
|
3475
|
+
if (result === void 0) return { hasContext: false };
|
|
3476
|
+
const contextDescriptor = Object.getOwnPropertyDescriptor(result, "context");
|
|
3477
|
+
if (contextDescriptor === void 0) return { hasContext: false };
|
|
3478
|
+
if (!("value" in contextDescriptor)) {
|
|
3479
|
+
throw new InvalidDomainTransitionResultError(
|
|
3480
|
+
"Domain transition result context must be a data property when provided."
|
|
3481
|
+
);
|
|
3482
|
+
}
|
|
3483
|
+
return { hasContext: true, context: contextDescriptor.value };
|
|
3484
|
+
}
|
|
3485
|
+
__name(readDomainTransitionResultContext, "readDomainTransitionResultContext");
|
|
3486
|
+
function readDomainTransitionResultOutputs(result) {
|
|
3487
|
+
if (result === void 0) return void 0;
|
|
3488
|
+
const outputsDescriptor = Object.getOwnPropertyDescriptor(result, "outputs");
|
|
3489
|
+
if (outputsDescriptor === void 0) return void 0;
|
|
3490
|
+
if (!("value" in outputsDescriptor)) {
|
|
3491
|
+
throw new InvalidDomainTransitionResultError(
|
|
3492
|
+
"Domain transition result outputs must be a data property when provided."
|
|
3493
|
+
);
|
|
3494
|
+
}
|
|
3495
|
+
return outputsDescriptor.value;
|
|
3496
|
+
}
|
|
3497
|
+
__name(readDomainTransitionResultOutputs, "readDomainTransitionResultOutputs");
|
|
3498
|
+
|
|
3499
|
+
// src/domain-state-machine/transition.ts
|
|
3500
|
+
function createInitialDomainMachineSnapshot(definition) {
|
|
3501
|
+
const stableDefinition = ensureStableDomainMachineDefinition(definition);
|
|
3502
|
+
return createInitialDomainMachineSnapshotFromPrepared(stableDefinition);
|
|
3503
|
+
}
|
|
3504
|
+
__name(createInitialDomainMachineSnapshot, "createInitialDomainMachineSnapshot");
|
|
3505
|
+
function createInitialDomainMachineSnapshotFromPrepared(definition) {
|
|
3506
|
+
const snapshot = createDomainMachineSnapshot({
|
|
3507
|
+
state: definition.initial,
|
|
3508
|
+
context: definition.initialContext()
|
|
3509
|
+
});
|
|
3510
|
+
validateDomainMachineSnapshotInvariant(definition, snapshot);
|
|
3511
|
+
return snapshot;
|
|
3512
|
+
}
|
|
3513
|
+
__name(createInitialDomainMachineSnapshotFromPrepared, "createInitialDomainMachineSnapshotFromPrepared");
|
|
3514
|
+
function canTransitionDomainState(definition, snapshot, input) {
|
|
3515
|
+
const stableDefinition = ensureStableDomainMachineDefinition(definition);
|
|
3516
|
+
const currentSnapshot = prepareDomainMachineSnapshot(
|
|
3517
|
+
stableDefinition,
|
|
3518
|
+
snapshot
|
|
3519
|
+
);
|
|
3520
|
+
return canTransitionPreparedDomainState(
|
|
3521
|
+
stableDefinition,
|
|
3522
|
+
currentSnapshot,
|
|
3523
|
+
input
|
|
3524
|
+
);
|
|
3525
|
+
}
|
|
3526
|
+
__name(canTransitionDomainState, "canTransitionDomainState");
|
|
3527
|
+
function canTransitionPreparedDomainState(definition, snapshot, input) {
|
|
3528
|
+
if (!isDomainMachineInput(input)) return false;
|
|
3529
|
+
const stateNode = definition.states[snapshot.state];
|
|
3530
|
+
if (stateNode.terminal === true) return false;
|
|
3531
|
+
const transition = getTransition(definition, snapshot.state, input);
|
|
3532
|
+
if (!transition) return false;
|
|
3533
|
+
const currentInput = copyDomainMachineInput(input);
|
|
3534
|
+
if (!transition.guard) return true;
|
|
3535
|
+
const guardResult = transition.guard({
|
|
3536
|
+
state: snapshot.state,
|
|
3537
|
+
context: snapshot.context,
|
|
3538
|
+
input: currentInput
|
|
3539
|
+
});
|
|
3540
|
+
return resolveDomainTransitionGuardResult(guardResult).allowed;
|
|
3541
|
+
}
|
|
3542
|
+
__name(canTransitionPreparedDomainState, "canTransitionPreparedDomainState");
|
|
3543
|
+
function transitionDomainState(definition, snapshot, input) {
|
|
3544
|
+
const stableDefinition = ensureStableDomainMachineDefinition(definition);
|
|
3545
|
+
const currentSnapshot = prepareDomainMachineSnapshot(
|
|
3546
|
+
stableDefinition,
|
|
3547
|
+
snapshot
|
|
3548
|
+
);
|
|
3549
|
+
return transitionPreparedDomainState(
|
|
3550
|
+
stableDefinition,
|
|
3551
|
+
currentSnapshot,
|
|
3552
|
+
input
|
|
3553
|
+
);
|
|
3554
|
+
}
|
|
3555
|
+
__name(transitionDomainState, "transitionDomainState");
|
|
3556
|
+
function transitionPreparedDomainState(definition, snapshot, input) {
|
|
3557
|
+
validateDomainMachineInput(input);
|
|
3558
|
+
const from = snapshot.state;
|
|
3559
|
+
const stateNode = definition.states[from];
|
|
3560
|
+
const transition = stateNode.terminal === true ? void 0 : getTransition(definition, from, input);
|
|
3561
|
+
if (!transition) {
|
|
3562
|
+
throw new InvalidDomainTransitionError(from, input.type);
|
|
3563
|
+
}
|
|
3564
|
+
const currentInput = copyDomainMachineInput(input);
|
|
3565
|
+
const guardResult = transition.guard === void 0 ? true : transition.guard({
|
|
3566
|
+
state: from,
|
|
3567
|
+
context: snapshot.context,
|
|
3568
|
+
input: currentInput
|
|
3569
|
+
});
|
|
3570
|
+
const guardDecision = resolveDomainTransitionGuardResult(guardResult);
|
|
3571
|
+
if (!guardDecision.allowed) {
|
|
3572
|
+
if (guardDecision.rejection !== void 0) {
|
|
3573
|
+
throw guardDecision.rejection;
|
|
3574
|
+
}
|
|
3575
|
+
throw new DomainTransitionGuardRejectedError(from, currentInput.type);
|
|
3576
|
+
}
|
|
3577
|
+
const result = transition.reduce?.({
|
|
3578
|
+
state: from,
|
|
3579
|
+
context: snapshot.context,
|
|
3580
|
+
input: currentInput
|
|
3581
|
+
});
|
|
3582
|
+
validateDomainTransitionResult(result);
|
|
3583
|
+
const contextResult = readDomainTransitionResultContext(result);
|
|
3584
|
+
const nextContext = contextResult.hasContext ? contextResult.context : snapshot.context;
|
|
3585
|
+
const nextSnapshot = nextContext === snapshot.context ? createDomainMachineSnapshotFromPreparedContext(
|
|
3586
|
+
transition.target,
|
|
3587
|
+
snapshot.context
|
|
3588
|
+
) : createDomainMachineSnapshot({
|
|
3589
|
+
state: transition.target,
|
|
3590
|
+
context: nextContext
|
|
3591
|
+
});
|
|
3592
|
+
validateDomainMachineSnapshotInvariant(definition, nextSnapshot);
|
|
3593
|
+
return {
|
|
3594
|
+
from,
|
|
3595
|
+
to: transition.target,
|
|
3596
|
+
snapshot: nextSnapshot,
|
|
3597
|
+
outputs: copyDomainMachineOutputs(
|
|
3598
|
+
readDomainTransitionResultOutputs(result)
|
|
3599
|
+
)
|
|
3600
|
+
};
|
|
3601
|
+
}
|
|
3602
|
+
__name(transitionPreparedDomainState, "transitionPreparedDomainState");
|
|
3603
|
+
|
|
3604
|
+
// src/domain-state-machine/analyzer.ts
|
|
3605
|
+
function analyzeDomainMachineDefinition(definition) {
|
|
3606
|
+
validateDomainMachineDefinition(definition);
|
|
3607
|
+
const stableDefinition = copyDomainMachineDefinition(definition);
|
|
3608
|
+
const states = Object.keys(stableDefinition.states).sort(
|
|
3609
|
+
compareStrings
|
|
3610
|
+
);
|
|
3611
|
+
const outgoing = /* @__PURE__ */ new Map();
|
|
3612
|
+
const incoming = /* @__PURE__ */ new Map();
|
|
3613
|
+
const transitions = [];
|
|
3614
|
+
for (const state of states) {
|
|
3615
|
+
outgoing.set(state, []);
|
|
3616
|
+
incoming.set(state, []);
|
|
3617
|
+
}
|
|
3618
|
+
for (const state of states) {
|
|
3619
|
+
const stateTransitions = stableDefinition.states[state].on;
|
|
3620
|
+
const inputTypes = Object.keys(stateTransitions ?? {}).sort(compareStrings);
|
|
3621
|
+
for (const inputType of inputTypes) {
|
|
3622
|
+
const transition = stateTransitions?.[inputType];
|
|
3623
|
+
if (transition === void 0) continue;
|
|
3624
|
+
outgoing.get(state)?.push(transition.target);
|
|
3625
|
+
incoming.get(transition.target)?.push(state);
|
|
3626
|
+
transitions.push(
|
|
3627
|
+
Object.freeze({
|
|
3628
|
+
state,
|
|
3629
|
+
inputType,
|
|
3630
|
+
target: transition.target,
|
|
3631
|
+
guarded: transition.guard !== void 0
|
|
3632
|
+
})
|
|
3633
|
+
);
|
|
3634
|
+
}
|
|
3635
|
+
}
|
|
3636
|
+
const structurallyReachable = visitGraph(
|
|
3637
|
+
[stableDefinition.initial],
|
|
3638
|
+
outgoing
|
|
3639
|
+
);
|
|
3640
|
+
const terminalStates = states.filter(
|
|
3641
|
+
(state) => stableDefinition.states[state].terminal === true
|
|
3642
|
+
);
|
|
3643
|
+
const statesWithTerminalPath = visitGraph(terminalStates, incoming);
|
|
3644
|
+
const diagnostics = [];
|
|
3645
|
+
for (const state of states) {
|
|
3646
|
+
if (!structurallyReachable.has(state)) {
|
|
3647
|
+
diagnostics.push(Object.freeze({ code: "unreachable-state", state }));
|
|
3648
|
+
}
|
|
3649
|
+
}
|
|
3650
|
+
for (const state of states) {
|
|
3651
|
+
if (stableDefinition.states[state].terminal !== true && outgoing.get(state)?.length === 0) {
|
|
3652
|
+
diagnostics.push(Object.freeze({ code: "structural-dead-end", state }));
|
|
3653
|
+
}
|
|
3654
|
+
}
|
|
3655
|
+
for (const state of states) {
|
|
3656
|
+
if (!statesWithTerminalPath.has(state)) {
|
|
3657
|
+
diagnostics.push(Object.freeze({ code: "no-terminal-path", state }));
|
|
3658
|
+
}
|
|
3659
|
+
}
|
|
3660
|
+
return Object.freeze({
|
|
3661
|
+
diagnostics: Object.freeze(diagnostics),
|
|
3662
|
+
transitions: Object.freeze(transitions),
|
|
3663
|
+
structurallyReachableStates: Object.freeze(
|
|
3664
|
+
states.filter((state) => structurallyReachable.has(state))
|
|
3665
|
+
),
|
|
3666
|
+
statesWithTerminalPath: Object.freeze(
|
|
3667
|
+
states.filter((state) => statesWithTerminalPath.has(state))
|
|
3668
|
+
)
|
|
3669
|
+
});
|
|
3670
|
+
}
|
|
3671
|
+
__name(analyzeDomainMachineDefinition, "analyzeDomainMachineDefinition");
|
|
3672
|
+
function visitGraph(startStates, edges) {
|
|
3673
|
+
const visited = /* @__PURE__ */ new Set();
|
|
3674
|
+
const pending = [...startStates];
|
|
3675
|
+
while (pending.length > 0) {
|
|
3676
|
+
const state = pending.pop();
|
|
3677
|
+
if (state === void 0 || visited.has(state)) continue;
|
|
3678
|
+
visited.add(state);
|
|
3679
|
+
for (const next of edges.get(state) ?? []) {
|
|
3680
|
+
if (!visited.has(next)) pending.push(next);
|
|
3681
|
+
}
|
|
3682
|
+
}
|
|
3683
|
+
return visited;
|
|
3684
|
+
}
|
|
3685
|
+
__name(visitGraph, "visitGraph");
|
|
3686
|
+
function compareStrings(left, right) {
|
|
3687
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
3688
|
+
}
|
|
3689
|
+
__name(compareStrings, "compareStrings");
|
|
3690
|
+
|
|
3691
|
+
// src/domain-state-machine/domain-state-machine.ts
|
|
3692
|
+
var DomainStateMachine = class {
|
|
3693
|
+
static {
|
|
3694
|
+
__name(this, "DomainStateMachine");
|
|
3695
|
+
}
|
|
3696
|
+
definition;
|
|
3697
|
+
#snapshot;
|
|
3698
|
+
#evaluating = false;
|
|
3699
|
+
constructor(definition, ...snapshotInput) {
|
|
3700
|
+
this.definition = ensureStableDomainMachineDefinition(definition);
|
|
3701
|
+
const [suppliedSnapshot] = snapshotInput;
|
|
3702
|
+
if (suppliedSnapshot !== void 0) {
|
|
3703
|
+
validateDomainMachineSnapshot(this.definition, suppliedSnapshot);
|
|
3704
|
+
this.#snapshot = createDomainMachineSnapshot(
|
|
3705
|
+
suppliedSnapshot
|
|
3706
|
+
);
|
|
3707
|
+
validateDomainMachineSnapshotInvariant(this.definition, this.#snapshot);
|
|
3708
|
+
} else {
|
|
3709
|
+
this.#snapshot = createInitialDomainMachineSnapshotFromPrepared(
|
|
3710
|
+
this.definition
|
|
3711
|
+
);
|
|
3712
|
+
}
|
|
3713
|
+
}
|
|
3714
|
+
/** Returns a defensive, deeply frozen copy of the current snapshot. */
|
|
3715
|
+
get snapshot() {
|
|
3716
|
+
return createDomainMachineSnapshot(this.#snapshot);
|
|
3717
|
+
}
|
|
3718
|
+
/** Current named control state. */
|
|
3719
|
+
get state() {
|
|
3720
|
+
return this.#snapshot.state;
|
|
3721
|
+
}
|
|
3722
|
+
/** Current deeply readonly context. */
|
|
3723
|
+
get context() {
|
|
3724
|
+
return this.#snapshot.context;
|
|
3725
|
+
}
|
|
3726
|
+
/** Whether the current state permanently forbids outgoing transitions. */
|
|
3727
|
+
isTerminal() {
|
|
3728
|
+
return this.definition.states[this.state].terminal === true;
|
|
3729
|
+
}
|
|
3730
|
+
/** Checks a transition without changing the current snapshot. */
|
|
3731
|
+
can(input) {
|
|
3732
|
+
return this.evaluate(
|
|
3733
|
+
() => canTransitionPreparedDomainState(this.definition, this.#snapshot, input)
|
|
3734
|
+
);
|
|
3735
|
+
}
|
|
3736
|
+
/** Applies an input and advances the current snapshot on success. */
|
|
3737
|
+
dispatch(input) {
|
|
3738
|
+
return this.evaluate(() => {
|
|
3739
|
+
const result = transitionPreparedDomainState(
|
|
3740
|
+
this.definition,
|
|
3741
|
+
this.#snapshot,
|
|
3742
|
+
input
|
|
3743
|
+
);
|
|
3744
|
+
this.#snapshot = result.snapshot;
|
|
3745
|
+
return result;
|
|
3746
|
+
});
|
|
3747
|
+
}
|
|
3748
|
+
evaluate(operation) {
|
|
3749
|
+
if (this.#evaluating) {
|
|
3750
|
+
throw new ReentrantDomainStateMachineEvaluationError();
|
|
3751
|
+
}
|
|
3752
|
+
this.#evaluating = true;
|
|
3753
|
+
try {
|
|
3754
|
+
return operation();
|
|
3755
|
+
} finally {
|
|
3756
|
+
this.#evaluating = false;
|
|
3757
|
+
}
|
|
3758
|
+
}
|
|
3759
|
+
};
|
|
3760
|
+
|
|
3761
|
+
// src/events/event-bus.ts
|
|
3762
|
+
var EventBusImpl = class {
|
|
3763
|
+
static {
|
|
3764
|
+
__name(this, "EventBusImpl");
|
|
3765
|
+
}
|
|
3766
|
+
handlers = /* @__PURE__ */ new Map();
|
|
3767
|
+
subscribe(eventType, handler) {
|
|
3768
|
+
const type = eventType;
|
|
3769
|
+
if (!this.handlers.has(type)) {
|
|
3770
|
+
this.handlers.set(type, []);
|
|
3771
|
+
}
|
|
3772
|
+
const handlersForType = this.handlers.get(type);
|
|
3773
|
+
const casted = handler;
|
|
3774
|
+
handlersForType.push(casted);
|
|
3775
|
+
let removed = false;
|
|
3776
|
+
return () => {
|
|
3777
|
+
if (removed) return;
|
|
3778
|
+
const idx = handlersForType.indexOf(casted);
|
|
3779
|
+
if (idx !== -1) {
|
|
3780
|
+
handlersForType.splice(idx, 1);
|
|
3781
|
+
removed = true;
|
|
3782
|
+
}
|
|
3783
|
+
if (handlersForType.length === 0) {
|
|
3784
|
+
this.handlers.delete(type);
|
|
3785
|
+
}
|
|
3786
|
+
};
|
|
3787
|
+
}
|
|
3788
|
+
once(eventType, options) {
|
|
3789
|
+
return new Promise((resolve, reject) => {
|
|
3790
|
+
if (options?.signal?.aborted) {
|
|
3791
|
+
reject(abortReason(options.signal, "EventBus.once aborted"));
|
|
3792
|
+
return;
|
|
3793
|
+
}
|
|
3794
|
+
let timer;
|
|
2241
3795
|
let settled = false;
|
|
2242
3796
|
let abortListener;
|
|
2243
3797
|
const cleanup = /* @__PURE__ */ __name(() => {
|
|
@@ -2319,21 +3873,98 @@ var InMemoryOutbox = class {
|
|
|
2319
3873
|
__name(this, "InMemoryOutbox");
|
|
2320
3874
|
}
|
|
2321
3875
|
pending = /* @__PURE__ */ new Map();
|
|
3876
|
+
dead = /* @__PURE__ */ new Map();
|
|
3877
|
+
maxDeliveryAttempts;
|
|
3878
|
+
constructor(options) {
|
|
3879
|
+
const max = options?.maxDeliveryAttempts ?? 5;
|
|
3880
|
+
if (!Number.isInteger(max) || max < 1) {
|
|
3881
|
+
throw new Error(
|
|
3882
|
+
`InMemoryOutbox: maxDeliveryAttempts must be an integer >= 1, got ${max}`
|
|
3883
|
+
);
|
|
3884
|
+
}
|
|
3885
|
+
this.maxDeliveryAttempts = max;
|
|
3886
|
+
}
|
|
2322
3887
|
async add(events) {
|
|
2323
3888
|
for (const event of events) {
|
|
3889
|
+
if (this.dead.has(event.eventId)) {
|
|
3890
|
+
this.dead.delete(event.eventId);
|
|
3891
|
+
this.pending.set(event.eventId, {
|
|
3892
|
+
dispatchId: event.eventId,
|
|
3893
|
+
event,
|
|
3894
|
+
attempts: 0
|
|
3895
|
+
});
|
|
3896
|
+
continue;
|
|
3897
|
+
}
|
|
3898
|
+
const existing = this.pending.get(event.eventId);
|
|
3899
|
+
if (existing) {
|
|
3900
|
+
existing.event = event;
|
|
3901
|
+
continue;
|
|
3902
|
+
}
|
|
2324
3903
|
this.pending.set(event.eventId, {
|
|
2325
3904
|
dispatchId: event.eventId,
|
|
2326
|
-
event
|
|
3905
|
+
event,
|
|
3906
|
+
attempts: 0
|
|
2327
3907
|
});
|
|
2328
3908
|
}
|
|
2329
3909
|
}
|
|
2330
3910
|
async getPending(limit) {
|
|
2331
|
-
const all = [...this.pending.values()]
|
|
3911
|
+
const all = [...this.pending.values()].map((record) => ({
|
|
3912
|
+
dispatchId: record.dispatchId,
|
|
3913
|
+
event: record.event,
|
|
3914
|
+
attempts: record.attempts
|
|
3915
|
+
}));
|
|
2332
3916
|
if (typeof limit !== "number") return all;
|
|
2333
3917
|
return all.slice(0, Math.max(0, limit));
|
|
2334
3918
|
}
|
|
2335
3919
|
async markDispatched(dispatchIds) {
|
|
2336
|
-
for (const id of dispatchIds)
|
|
3920
|
+
for (const id of dispatchIds) {
|
|
3921
|
+
this.pending.delete(id);
|
|
3922
|
+
this.dead.delete(id);
|
|
3923
|
+
}
|
|
3924
|
+
}
|
|
3925
|
+
async markFailed(dispatchId, error) {
|
|
3926
|
+
const record = this.pending.get(dispatchId);
|
|
3927
|
+
if (!record) return;
|
|
3928
|
+
record.attempts += 1;
|
|
3929
|
+
record.lastError = error instanceof Error ? error.message : String(error ?? "unknown");
|
|
3930
|
+
if (record.attempts >= this.maxDeliveryAttempts) {
|
|
3931
|
+
this.pending.delete(dispatchId);
|
|
3932
|
+
this.dead.set(dispatchId, {
|
|
3933
|
+
dispatchId: record.dispatchId,
|
|
3934
|
+
event: record.event,
|
|
3935
|
+
attempts: record.attempts,
|
|
3936
|
+
lastError: record.lastError
|
|
3937
|
+
});
|
|
3938
|
+
}
|
|
3939
|
+
}
|
|
3940
|
+
async deadLetters() {
|
|
3941
|
+
return [...this.dead.values()].map((record) => ({ ...record }));
|
|
3942
|
+
}
|
|
3943
|
+
};
|
|
3944
|
+
|
|
3945
|
+
// src/repo/in-memory-event-store.ts
|
|
3946
|
+
var InMemoryEventStore = class {
|
|
3947
|
+
static {
|
|
3948
|
+
__name(this, "InMemoryEventStore");
|
|
3949
|
+
}
|
|
3950
|
+
streams = /* @__PURE__ */ new Map();
|
|
3951
|
+
async append(streamId, events, options) {
|
|
3952
|
+
if (events.length === 0) return;
|
|
3953
|
+
const stream = this.streams.get(streamId) ?? [];
|
|
3954
|
+
if (stream.length !== options.expectedVersion) {
|
|
3955
|
+
throw new ConcurrencyConflictError({
|
|
3956
|
+
aggregateType: events[0]?.aggregateType ?? "stream",
|
|
3957
|
+
aggregateId: streamId,
|
|
3958
|
+
expectedVersion: options.expectedVersion,
|
|
3959
|
+
actualVersion: stream.length
|
|
3960
|
+
});
|
|
3961
|
+
}
|
|
3962
|
+
this.streams.set(streamId, [...stream, ...events]);
|
|
3963
|
+
}
|
|
3964
|
+
async readStream(streamId, options) {
|
|
3965
|
+
const stream = this.streams.get(streamId) ?? [];
|
|
3966
|
+
const fromVersion = options?.fromVersion ?? 0;
|
|
3967
|
+
return stream.slice(Math.max(0, fromVersion));
|
|
2337
3968
|
}
|
|
2338
3969
|
};
|
|
2339
3970
|
var DEFAULT_MAX_ATTEMPTS = 3;
|
|
@@ -2424,7 +4055,7 @@ var RetryingTransactionScope = class {
|
|
|
2424
4055
|
maxDelayMs: this.maxDelayMs,
|
|
2425
4056
|
random: this.random
|
|
2426
4057
|
});
|
|
2427
|
-
this.onRetry?.({ attempt, error, delayMs });
|
|
4058
|
+
reportToObserver(() => this.onRetry?.({ attempt, error, delayMs }));
|
|
2428
4059
|
await sleep(delayMs, signal);
|
|
2429
4060
|
}
|
|
2430
4061
|
}
|
|
@@ -2438,6 +4069,6 @@ function voValidated(t, validate, message = "Validation failed") {
|
|
|
2438
4069
|
}
|
|
2439
4070
|
__name(voValidated, "voValidated");
|
|
2440
4071
|
|
|
2441
|
-
export { AggregateDeletedError, AggregateNotFoundError, AggregateRoot, CommandBus, CommitError, ConcurrencyConflictError, DomainError, DuplicateAggregateError, Entity, EventBusImpl, EventHarvestError, EventSourcedAggregate, IdentityMap, InMemoryOutbox, InfrastructureError, MissingHandlerError, NestedUnitOfWorkError, QueryBus, RetryingTransactionScope, RollbackError, TransactionClosedError, UnenrolledChangesError, UnitOfWork, ValueObject, computeBackoffDelay, copyMetadata, createDomainEvent, deepEqual, deepEqualExcept, deepFreeze, deepOmit, entityIds, findEntityById, freezeShallow, hasEntityId, mergeMetadata, removeEntityById, replaceEntityById, resetClockFactory, resetEventIdFactory, sameEntity, sameVersion, setClockFactory, setEventIdFactory, updateEntityById, vo, voEquals, voEqualsExcept, voValidated, voWithValidation, withClockFactory, withCommit, withEventIdFactory };
|
|
4072
|
+
export { AggregateDeletedError, AggregateNotFoundError, AggregateRoot, CommandBus, CommitError, ConcurrencyConflictError, DomainError, DomainStateMachine, DomainTransitionGuardRejectedError, DuplicateAggregateError, Entity, EventBusImpl, EventHarvestError, EventSourcedAggregate, IdentityMap, InMemoryEventStore, InMemoryOutbox, InfrastructureError, InvalidDomainMachineContextError, InvalidDomainMachineDefinitionError, InvalidDomainMachineInputError, InvalidDomainMachineSnapshotError, InvalidDomainTransitionError, InvalidDomainTransitionGuardResultError, InvalidDomainTransitionResultError, MissingHandlerError, NestedUnitOfWorkError, QueryBus, ReentrantDomainStateMachineEvaluationError, RetryingTransactionScope, RollbackError, SnapshotSchemaMismatchError, TransactionClosedError, UnenrolledChangesError, UnitOfWork, UnregisteredHandlerError, UnreplayableAggregateError, ValueObject, analyzeDomainMachineDefinition, canTransitionDomainState, computeBackoffDelay, copyMetadata, createDomainEvent, createInitialDomainMachineSnapshot, deepEqual, deepEqualExcept, deepFreeze, deepOmit, entityIds, findEntityById, freezeShallow, hasEntityId, mergeMetadata, prepareDomainMachineDefinition, removeEntityById, replaceEntityById, resetClockFactory, resetEventIdFactory, sameEntity, sameVersion, setClockFactory, setEventIdFactory, transitionDomainState, updateEntityById, vo, voEquals, voEqualsExcept, voValidated, voWithValidation, withClockFactory, withCommit, withEventIdFactory };
|
|
2442
4073
|
//# sourceMappingURL=index.js.map
|
|
2443
4074
|
//# sourceMappingURL=index.js.map
|