@shirudo/ddd-kit 1.0.1 → 1.1.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/dist/index.d.ts +106 -11
- package/dist/index.js +520 -79
- package/dist/index.js.map +1 -1
- package/dist/utils.d.ts +15 -3
- package/dist/utils.js +158 -53
- package/dist/utils.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ok, err } from '@shirudo/result';
|
|
2
2
|
import { BaseError, ValidationError } from '@shirudo/base-error';
|
|
3
3
|
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
@@ -21,10 +21,83 @@ var BUILT_IN_TAGS = /* @__PURE__ */ new Set([
|
|
|
21
21
|
"[object SharedArrayBuffer]",
|
|
22
22
|
"[object DataView]"
|
|
23
23
|
]);
|
|
24
|
+
function intrinsicGetter(proto, prop) {
|
|
25
|
+
const get = Object.getOwnPropertyDescriptor(proto, prop)?.get;
|
|
26
|
+
if (!get) throw new Error(`missing intrinsic getter for ${prop}`);
|
|
27
|
+
return get;
|
|
28
|
+
}
|
|
29
|
+
__name(intrinsicGetter, "intrinsicGetter");
|
|
30
|
+
var dateGetTime = Date.prototype.getTime;
|
|
31
|
+
var mapSizeGet = intrinsicGetter(Map.prototype, "size");
|
|
32
|
+
var setSizeGet = intrinsicGetter(Set.prototype, "size");
|
|
33
|
+
var weakMapHas = WeakMap.prototype.has;
|
|
34
|
+
var weakSetHas = WeakSet.prototype.has;
|
|
35
|
+
var dataViewByteLengthGet = intrinsicGetter(DataView.prototype, "byteLength");
|
|
36
|
+
var arrayBufferByteLengthGet = intrinsicGetter(
|
|
37
|
+
ArrayBuffer.prototype,
|
|
38
|
+
"byteLength"
|
|
39
|
+
);
|
|
40
|
+
var regExpSourceGet = intrinsicGetter(RegExp.prototype, "source");
|
|
41
|
+
var booleanValueOf = Boolean.prototype.valueOf;
|
|
42
|
+
var numberValueOf = Number.prototype.valueOf;
|
|
43
|
+
var stringValueOf = String.prototype.valueOf;
|
|
44
|
+
var PROBE_KEY = {};
|
|
45
|
+
var REFERENCE_COMPARED_TAGS = /* @__PURE__ */ new Set([
|
|
46
|
+
"[object Error]",
|
|
47
|
+
"[object ArrayBuffer]",
|
|
48
|
+
"[object SharedArrayBuffer]",
|
|
49
|
+
"[object Promise]",
|
|
50
|
+
"[object WeakMap]",
|
|
51
|
+
"[object WeakSet]"
|
|
52
|
+
]);
|
|
53
|
+
function hasBrand(obj, tag) {
|
|
54
|
+
try {
|
|
55
|
+
switch (tag) {
|
|
56
|
+
case "[object Date]":
|
|
57
|
+
dateGetTime.call(obj);
|
|
58
|
+
return true;
|
|
59
|
+
case "[object RegExp]":
|
|
60
|
+
regExpSourceGet.call(obj);
|
|
61
|
+
return true;
|
|
62
|
+
case "[object Map]":
|
|
63
|
+
mapSizeGet.call(obj);
|
|
64
|
+
return true;
|
|
65
|
+
case "[object Set]":
|
|
66
|
+
setSizeGet.call(obj);
|
|
67
|
+
return true;
|
|
68
|
+
case "[object WeakMap]":
|
|
69
|
+
weakMapHas.call(obj, PROBE_KEY);
|
|
70
|
+
return true;
|
|
71
|
+
case "[object WeakSet]":
|
|
72
|
+
weakSetHas.call(obj, PROBE_KEY);
|
|
73
|
+
return true;
|
|
74
|
+
case "[object DataView]":
|
|
75
|
+
dataViewByteLengthGet.call(obj);
|
|
76
|
+
return true;
|
|
77
|
+
case "[object ArrayBuffer]":
|
|
78
|
+
arrayBufferByteLengthGet.call(obj);
|
|
79
|
+
return true;
|
|
80
|
+
case "[object Boolean]":
|
|
81
|
+
booleanValueOf.call(obj);
|
|
82
|
+
return true;
|
|
83
|
+
case "[object Number]":
|
|
84
|
+
numberValueOf.call(obj);
|
|
85
|
+
return true;
|
|
86
|
+
case "[object String]":
|
|
87
|
+
stringValueOf.call(obj);
|
|
88
|
+
return true;
|
|
89
|
+
default:
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
} catch {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
__name(hasBrand, "hasBrand");
|
|
24
97
|
function isBuiltInObject(obj, tag) {
|
|
25
|
-
if (tag.endsWith("Array]")) return true;
|
|
26
98
|
if (ArrayBuffer.isView(obj)) return true;
|
|
27
|
-
|
|
99
|
+
if (tag.endsWith("Array]")) return false;
|
|
100
|
+
return BUILT_IN_TAGS.has(tag) && hasBrand(obj, tag);
|
|
28
101
|
}
|
|
29
102
|
__name(isBuiltInObject, "isBuiltInObject");
|
|
30
103
|
|
|
@@ -32,6 +105,10 @@ __name(isBuiltInObject, "isBuiltInObject");
|
|
|
32
105
|
var objProto = Object.prototype;
|
|
33
106
|
var objToString = objProto.toString;
|
|
34
107
|
var objHasOwn = objProto.hasOwnProperty;
|
|
108
|
+
function sameValueZero(a, b) {
|
|
109
|
+
return a === b || Number.isNaN(a) && Number.isNaN(b);
|
|
110
|
+
}
|
|
111
|
+
__name(sameValueZero, "sameValueZero");
|
|
35
112
|
function deepEqual(a, b) {
|
|
36
113
|
return deepEqualInner(a, b, /* @__PURE__ */ new WeakMap());
|
|
37
114
|
}
|
|
@@ -77,24 +154,31 @@ function deepEqualInner(a, b, visited) {
|
|
|
77
154
|
const len = arrA.length;
|
|
78
155
|
if (len !== arrB.length) return false;
|
|
79
156
|
for (let i = 0; i < len; i++) {
|
|
80
|
-
if (arrA[i]
|
|
157
|
+
if (!sameValueZero(arrA[i], arrB[i])) return false;
|
|
158
|
+
}
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
if (Array.isArray(objA) || Array.isArray(objB)) {
|
|
162
|
+
if (!Array.isArray(objA) || !Array.isArray(objB)) return false;
|
|
163
|
+
const arrA = objA;
|
|
164
|
+
const arrB = objB;
|
|
165
|
+
const len = arrA.length;
|
|
166
|
+
if (len !== arrB.length) return false;
|
|
167
|
+
for (let i = 0; i < len; i++) {
|
|
168
|
+
if (!deepEqualInner(arrA[i], arrB[i], visited)) return false;
|
|
81
169
|
}
|
|
82
170
|
return true;
|
|
83
171
|
}
|
|
84
172
|
const tagA = objToString.call(objA);
|
|
85
173
|
const tagB = objToString.call(objB);
|
|
86
174
|
if (tagA !== tagB) return false;
|
|
175
|
+
const builtInA = isBuiltInObject(objA, tagA);
|
|
176
|
+
const builtInB = isBuiltInObject(objB, tagB);
|
|
177
|
+
if (builtInA !== builtInB) return false;
|
|
178
|
+
if (!builtInA) {
|
|
179
|
+
return comparePlainObjects(objA, objB, visited);
|
|
180
|
+
}
|
|
87
181
|
switch (tagA) {
|
|
88
|
-
case "[object Array]": {
|
|
89
|
-
const arrA = objA;
|
|
90
|
-
const arrB = objB;
|
|
91
|
-
const len = arrA.length;
|
|
92
|
-
if (len !== arrB.length) return false;
|
|
93
|
-
for (let i = 0; i < len; i++) {
|
|
94
|
-
if (!deepEqualInner(arrA[i], arrB[i], visited)) return false;
|
|
95
|
-
}
|
|
96
|
-
return true;
|
|
97
|
-
}
|
|
98
182
|
case "[object Map]": {
|
|
99
183
|
const mapA = objA;
|
|
100
184
|
const mapB = objB;
|
|
@@ -116,9 +200,7 @@ function deepEqualInner(a, b, visited) {
|
|
|
116
200
|
return true;
|
|
117
201
|
}
|
|
118
202
|
case "[object Date]": {
|
|
119
|
-
|
|
120
|
-
const timeB = objB.getTime();
|
|
121
|
-
return timeA === timeB;
|
|
203
|
+
return sameValueZero(objA.getTime(), objB.getTime());
|
|
122
204
|
}
|
|
123
205
|
case "[object RegExp]": {
|
|
124
206
|
const regA = objA;
|
|
@@ -128,69 +210,88 @@ function deepEqualInner(a, b, visited) {
|
|
|
128
210
|
case "[object Boolean]":
|
|
129
211
|
case "[object Number]":
|
|
130
212
|
case "[object String]": {
|
|
131
|
-
return
|
|
213
|
+
return sameValueZero(
|
|
214
|
+
objA.valueOf(),
|
|
215
|
+
objB.valueOf()
|
|
216
|
+
);
|
|
132
217
|
}
|
|
133
218
|
default: {
|
|
134
|
-
|
|
135
|
-
return objA === objB;
|
|
136
|
-
}
|
|
137
|
-
const recA = objA;
|
|
138
|
-
const recB = objB;
|
|
139
|
-
const stringKeysA = Object.keys(objA);
|
|
140
|
-
const stringKeysB = Object.keys(objB);
|
|
141
|
-
if (stringKeysA.length !== stringKeysB.length) return false;
|
|
142
|
-
const symbolKeysA = Object.getOwnPropertySymbols(objA);
|
|
143
|
-
const symbolKeysB = Object.getOwnPropertySymbols(objB);
|
|
144
|
-
if (symbolKeysA.length !== symbolKeysB.length) return false;
|
|
145
|
-
const symbolKeysBSet = new Set(symbolKeysB);
|
|
146
|
-
for (const key of stringKeysA) {
|
|
147
|
-
if (!objHasOwn.call(objB, key)) return false;
|
|
148
|
-
}
|
|
149
|
-
for (const key of symbolKeysA) {
|
|
150
|
-
if (!symbolKeysBSet.has(key)) return false;
|
|
151
|
-
}
|
|
152
|
-
for (const key of stringKeysA) {
|
|
153
|
-
if (!deepEqualInner(recA[key], recB[key], visited)) {
|
|
154
|
-
return false;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
for (const key of symbolKeysA) {
|
|
158
|
-
if (!deepEqualInner(recA[key], recB[key], visited)) {
|
|
159
|
-
return false;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
return true;
|
|
219
|
+
return objA === objB;
|
|
163
220
|
}
|
|
164
221
|
}
|
|
165
222
|
}
|
|
166
223
|
__name(deepEqualInner, "deepEqualInner");
|
|
224
|
+
function comparePlainObjects(objA, objB, visited) {
|
|
225
|
+
const recA = objA;
|
|
226
|
+
const recB = objB;
|
|
227
|
+
const stringKeysA = Object.keys(objA);
|
|
228
|
+
const stringKeysB = Object.keys(objB);
|
|
229
|
+
if (stringKeysA.length !== stringKeysB.length) return false;
|
|
230
|
+
const symbolKeysA = Object.getOwnPropertySymbols(objA);
|
|
231
|
+
const symbolKeysB = Object.getOwnPropertySymbols(objB);
|
|
232
|
+
if (symbolKeysA.length !== symbolKeysB.length) return false;
|
|
233
|
+
const symbolKeysBSet = new Set(symbolKeysB);
|
|
234
|
+
for (const key of stringKeysA) {
|
|
235
|
+
if (!objHasOwn.call(objB, key)) return false;
|
|
236
|
+
}
|
|
237
|
+
for (const key of symbolKeysA) {
|
|
238
|
+
if (!symbolKeysBSet.has(key)) return false;
|
|
239
|
+
}
|
|
240
|
+
for (const key of stringKeysA) {
|
|
241
|
+
if (!deepEqualInner(recA[key], recB[key], visited)) {
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
for (const key of symbolKeysA) {
|
|
246
|
+
if (!deepEqualInner(recA[key], recB[key], visited)) {
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
__name(comparePlainObjects, "comparePlainObjects");
|
|
167
253
|
|
|
168
254
|
// src/utils/array/deep-omit.ts
|
|
169
255
|
function deepOmit(value, options) {
|
|
170
256
|
const visited = /* @__PURE__ */ new WeakMap();
|
|
171
257
|
const ignoreKeys = options.ignoreKeys ? new Set(options.ignoreKeys) : void 0;
|
|
172
|
-
|
|
258
|
+
const budget = options.ignoreKeyPredicate ? { visits: 0 } : void 0;
|
|
259
|
+
return omitInternal(value, options, ignoreKeys, [], visited, budget);
|
|
173
260
|
}
|
|
174
261
|
__name(deepOmit, "deepOmit");
|
|
175
|
-
|
|
262
|
+
var PATH_SENSITIVE_VISIT_BUDGET = 1e6;
|
|
263
|
+
function omitInternal(value, options, ignoreKeys, path, visited, budget) {
|
|
176
264
|
if (value === null) return value;
|
|
177
265
|
if (typeof value !== "object") return value;
|
|
178
266
|
const obj = value;
|
|
179
267
|
if (visited.has(obj)) {
|
|
180
268
|
return visited.get(obj);
|
|
181
269
|
}
|
|
182
|
-
|
|
183
|
-
|
|
270
|
+
if (budget && ++budget.visits > PATH_SENSITIVE_VISIT_BUDGET) {
|
|
271
|
+
throw new Error(
|
|
272
|
+
`deepOmit: exceeded ${PATH_SENSITIVE_VISIT_BUDGET} node visits. With ignoreKeyPredicate, objects reached via shared references are cloned once per path (the predicate may decide differently per path), which expands exponentially on diamond-shaped sharing. Restructure the input to a tree, or use ignoreKeys for path-independent filtering.`
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
if (Array.isArray(obj)) {
|
|
184
276
|
const arr = obj;
|
|
185
277
|
const clone2 = new Array(arr.length);
|
|
186
278
|
visited.set(obj, clone2);
|
|
187
279
|
for (let i = 0; i < arr.length; i++) {
|
|
188
280
|
path.push(i);
|
|
189
|
-
clone2[i] = omitInternal(
|
|
281
|
+
clone2[i] = omitInternal(
|
|
282
|
+
arr[i],
|
|
283
|
+
options,
|
|
284
|
+
ignoreKeys,
|
|
285
|
+
path,
|
|
286
|
+
visited,
|
|
287
|
+
budget
|
|
288
|
+
);
|
|
190
289
|
path.pop();
|
|
191
290
|
}
|
|
291
|
+
if (budget) visited.delete(obj);
|
|
192
292
|
return clone2;
|
|
193
293
|
}
|
|
294
|
+
const tag = Object.prototype.toString.call(obj);
|
|
194
295
|
if (isBuiltInObject(obj, tag)) {
|
|
195
296
|
const builtInClone = cloneBuiltIn(obj, tag);
|
|
196
297
|
visited.set(obj, builtInClone);
|
|
@@ -211,7 +312,8 @@ function omitInternal(value, options, ignoreKeys, path, visited) {
|
|
|
211
312
|
options,
|
|
212
313
|
ignoreKeys,
|
|
213
314
|
path,
|
|
214
|
-
visited
|
|
315
|
+
visited,
|
|
316
|
+
budget
|
|
215
317
|
)
|
|
216
318
|
);
|
|
217
319
|
path.pop();
|
|
@@ -227,11 +329,13 @@ function omitInternal(value, options, ignoreKeys, path, visited) {
|
|
|
227
329
|
options,
|
|
228
330
|
ignoreKeys,
|
|
229
331
|
path,
|
|
230
|
-
visited
|
|
332
|
+
visited,
|
|
333
|
+
budget
|
|
231
334
|
)
|
|
232
335
|
);
|
|
233
336
|
path.pop();
|
|
234
337
|
}
|
|
338
|
+
if (budget) visited.delete(obj);
|
|
235
339
|
return clone;
|
|
236
340
|
}
|
|
237
341
|
__name(omitInternal, "omitInternal");
|
|
@@ -245,6 +349,7 @@ function assignOwn(target, key, value) {
|
|
|
245
349
|
}
|
|
246
350
|
__name(assignOwn, "assignOwn");
|
|
247
351
|
function cloneBuiltIn(obj, tag) {
|
|
352
|
+
if (REFERENCE_COMPARED_TAGS.has(tag)) return obj;
|
|
248
353
|
switch (tag) {
|
|
249
354
|
case "[object Date]":
|
|
250
355
|
return new Date(obj.getTime());
|
|
@@ -281,14 +386,81 @@ function deepEqualExcept(a, b, options) {
|
|
|
281
386
|
return deepEqual(prunedA, prunedB);
|
|
282
387
|
}
|
|
283
388
|
__name(deepEqualExcept, "deepEqualExcept");
|
|
389
|
+
var DATE_MUTATORS = [
|
|
390
|
+
"setTime",
|
|
391
|
+
"setMilliseconds",
|
|
392
|
+
"setUTCMilliseconds",
|
|
393
|
+
"setSeconds",
|
|
394
|
+
"setUTCSeconds",
|
|
395
|
+
"setMinutes",
|
|
396
|
+
"setUTCMinutes",
|
|
397
|
+
"setHours",
|
|
398
|
+
"setUTCHours",
|
|
399
|
+
"setDate",
|
|
400
|
+
"setUTCDate",
|
|
401
|
+
"setMonth",
|
|
402
|
+
"setUTCMonth",
|
|
403
|
+
"setFullYear",
|
|
404
|
+
"setUTCFullYear",
|
|
405
|
+
"setYear"
|
|
406
|
+
];
|
|
407
|
+
var mutationThrowers = /* @__PURE__ */ new Map();
|
|
408
|
+
function mutationThrower(typeName, method) {
|
|
409
|
+
const key = `${typeName}.${method}`;
|
|
410
|
+
let thrower = mutationThrowers.get(key);
|
|
411
|
+
if (!thrower) {
|
|
412
|
+
thrower = /* @__PURE__ */ __name(function throwFrozenMutation() {
|
|
413
|
+
throw new TypeError(
|
|
414
|
+
`Cannot call ${method}() on a ${typeName} inside a deeply frozen value`
|
|
415
|
+
);
|
|
416
|
+
}, "throwFrozenMutation");
|
|
417
|
+
mutationThrowers.set(key, thrower);
|
|
418
|
+
}
|
|
419
|
+
return thrower;
|
|
420
|
+
}
|
|
421
|
+
__name(mutationThrower, "mutationThrower");
|
|
422
|
+
var shadowDescriptor = {
|
|
423
|
+
value: void 0,
|
|
424
|
+
writable: false,
|
|
425
|
+
enumerable: false,
|
|
426
|
+
configurable: false
|
|
427
|
+
};
|
|
428
|
+
function shadowMutators(obj, typeName, methods) {
|
|
429
|
+
if (!Object.isExtensible(obj)) return;
|
|
430
|
+
for (const method of methods) {
|
|
431
|
+
shadowDescriptor.value = mutationThrower(typeName, method);
|
|
432
|
+
Object.defineProperty(obj, method, shadowDescriptor);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
__name(shadowMutators, "shadowMutators");
|
|
284
436
|
function deepFreeze(obj, visited = /* @__PURE__ */ new WeakSet()) {
|
|
285
437
|
if (obj === null || typeof obj !== "object") {
|
|
286
438
|
return obj;
|
|
287
439
|
}
|
|
440
|
+
if (ArrayBuffer.isView(obj)) {
|
|
441
|
+
return obj;
|
|
442
|
+
}
|
|
288
443
|
if (visited.has(obj)) {
|
|
289
444
|
return obj;
|
|
290
445
|
}
|
|
291
446
|
visited.add(obj);
|
|
447
|
+
const tag = Object.prototype.toString.call(obj);
|
|
448
|
+
if (isBuiltInObject(obj, tag)) {
|
|
449
|
+
if (tag === "[object Date]") {
|
|
450
|
+
shadowMutators(obj, "Date", DATE_MUTATORS);
|
|
451
|
+
} else if (tag === "[object Map]") {
|
|
452
|
+
for (const [key, value] of obj) {
|
|
453
|
+
deepFreeze(key, visited);
|
|
454
|
+
deepFreeze(value, visited);
|
|
455
|
+
}
|
|
456
|
+
shadowMutators(obj, "Map", ["set", "delete", "clear"]);
|
|
457
|
+
} else if (tag === "[object Set]") {
|
|
458
|
+
for (const member of obj) {
|
|
459
|
+
deepFreeze(member, visited);
|
|
460
|
+
}
|
|
461
|
+
shadowMutators(obj, "Set", ["add", "delete", "clear"]);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
292
464
|
const keys = Reflect.ownKeys(obj);
|
|
293
465
|
for (const key of keys) {
|
|
294
466
|
const value = obj[key];
|
|
@@ -299,8 +471,74 @@ function deepFreeze(obj, visited = /* @__PURE__ */ new WeakSet()) {
|
|
|
299
471
|
return Object.freeze(obj);
|
|
300
472
|
}
|
|
301
473
|
__name(deepFreeze, "deepFreeze");
|
|
474
|
+
function cloneForVo(value, visited) {
|
|
475
|
+
if (typeof value === "function") {
|
|
476
|
+
throw new TypeError(
|
|
477
|
+
"vo() does not accept function values \u2014 Value Objects are data, not behaviour"
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
if (value === null || typeof value !== "object") {
|
|
481
|
+
return value;
|
|
482
|
+
}
|
|
483
|
+
const obj = value;
|
|
484
|
+
if (visited.has(obj)) {
|
|
485
|
+
return visited.get(obj);
|
|
486
|
+
}
|
|
487
|
+
if (Array.isArray(obj)) {
|
|
488
|
+
const clone2 = new Array(obj.length);
|
|
489
|
+
visited.set(obj, clone2);
|
|
490
|
+
for (let i = 0; i < obj.length; i++) {
|
|
491
|
+
clone2[i] = cloneForVo(obj[i], visited);
|
|
492
|
+
}
|
|
493
|
+
return clone2;
|
|
494
|
+
}
|
|
495
|
+
const tag = Object.prototype.toString.call(obj);
|
|
496
|
+
if (isBuiltInObject(obj, tag)) {
|
|
497
|
+
if (tag === "[object Map]") {
|
|
498
|
+
const clone2 = /* @__PURE__ */ new Map();
|
|
499
|
+
visited.set(obj, clone2);
|
|
500
|
+
for (const [key, entry] of obj) {
|
|
501
|
+
clone2.set(cloneForVo(key, visited), cloneForVo(entry, visited));
|
|
502
|
+
}
|
|
503
|
+
return clone2;
|
|
504
|
+
}
|
|
505
|
+
if (tag === "[object Set]") {
|
|
506
|
+
const clone2 = /* @__PURE__ */ new Set();
|
|
507
|
+
visited.set(obj, clone2);
|
|
508
|
+
for (const member of obj) {
|
|
509
|
+
clone2.add(cloneForVo(member, visited));
|
|
510
|
+
}
|
|
511
|
+
return clone2;
|
|
512
|
+
}
|
|
513
|
+
if (tag === "[object Promise]" || tag === "[object WeakMap]" || tag === "[object WeakSet]") {
|
|
514
|
+
throw new TypeError(
|
|
515
|
+
`vo() cannot clone a ${tag.slice(8, -1)} \u2014 Value Objects are plain data`
|
|
516
|
+
);
|
|
517
|
+
}
|
|
518
|
+
const builtInClone = structuredClone(obj);
|
|
519
|
+
visited.set(obj, builtInClone);
|
|
520
|
+
return builtInClone;
|
|
521
|
+
}
|
|
522
|
+
const clone = Object.create(Object.getPrototypeOf(obj));
|
|
523
|
+
visited.set(obj, clone);
|
|
524
|
+
for (const key of Reflect.ownKeys(obj)) {
|
|
525
|
+
const descriptor = Object.getOwnPropertyDescriptor(obj, key);
|
|
526
|
+
if (!descriptor?.enumerable) continue;
|
|
527
|
+
Object.defineProperty(clone, key, {
|
|
528
|
+
value: cloneForVo(
|
|
529
|
+
obj[key],
|
|
530
|
+
visited
|
|
531
|
+
),
|
|
532
|
+
writable: true,
|
|
533
|
+
enumerable: true,
|
|
534
|
+
configurable: true
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
return clone;
|
|
538
|
+
}
|
|
539
|
+
__name(cloneForVo, "cloneForVo");
|
|
302
540
|
function vo(t) {
|
|
303
|
-
return deepFreeze(
|
|
541
|
+
return deepFreeze(cloneForVo(t, /* @__PURE__ */ new WeakMap()));
|
|
304
542
|
}
|
|
305
543
|
__name(vo, "vo");
|
|
306
544
|
function voEquals(a, b) {
|
|
@@ -314,12 +552,21 @@ __name(voEqualsExcept, "voEqualsExcept");
|
|
|
314
552
|
function voWithValidation(t, validate, errorMessage) {
|
|
315
553
|
if (!validate(t)) {
|
|
316
554
|
return err(
|
|
317
|
-
errorMessage ?? `Validation failed for value object: ${
|
|
555
|
+
errorMessage ?? `Validation failed for value object: ${describeValue(t)}`
|
|
318
556
|
);
|
|
319
557
|
}
|
|
320
558
|
return ok(vo(t));
|
|
321
559
|
}
|
|
322
560
|
__name(voWithValidation, "voWithValidation");
|
|
561
|
+
function describeValue(value) {
|
|
562
|
+
try {
|
|
563
|
+
const json = JSON.stringify(value);
|
|
564
|
+
if (json !== void 0) return json;
|
|
565
|
+
} catch {
|
|
566
|
+
}
|
|
567
|
+
return String(value);
|
|
568
|
+
}
|
|
569
|
+
__name(describeValue, "describeValue");
|
|
323
570
|
var ValueObject = class {
|
|
324
571
|
static {
|
|
325
572
|
__name(this, "ValueObject");
|
|
@@ -327,7 +574,9 @@ var ValueObject = class {
|
|
|
327
574
|
props;
|
|
328
575
|
/**
|
|
329
576
|
* Creates a new ValueObject.
|
|
330
|
-
* The properties are
|
|
577
|
+
* The properties are deep-cloned (prototype-preserving) and then deeply
|
|
578
|
+
* frozen — the caller's own object graph is never frozen or mutated,
|
|
579
|
+
* and later mutation of the input does not bleed into the value object.
|
|
331
580
|
*
|
|
332
581
|
* @param props - The properties of the value object
|
|
333
582
|
* @example
|
|
@@ -345,7 +594,7 @@ var ValueObject = class {
|
|
|
345
594
|
*/
|
|
346
595
|
constructor(props) {
|
|
347
596
|
this.validate(props);
|
|
348
|
-
this.props = deepFreeze(
|
|
597
|
+
this.props = deepFreeze(cloneForVo(props, /* @__PURE__ */ new WeakMap()));
|
|
349
598
|
}
|
|
350
599
|
/**
|
|
351
600
|
* Optional validation hook that can be overridden by subclasses.
|
|
@@ -452,7 +701,9 @@ function createDomainEvent(type, payload, options) {
|
|
|
452
701
|
aggregateId: options?.aggregateId,
|
|
453
702
|
aggregateType: options?.aggregateType,
|
|
454
703
|
payload,
|
|
455
|
-
|
|
704
|
+
// Defensive copy — the event must not share the caller's live Date
|
|
705
|
+
// instance, or a later mutation of it would bleed into the event.
|
|
706
|
+
occurredAt: options?.occurredAt ? new Date(options.occurredAt.getTime()) : currentClockFactory(),
|
|
456
707
|
version: options?.version ?? 1,
|
|
457
708
|
metadata: options?.metadata
|
|
458
709
|
};
|
|
@@ -474,7 +725,21 @@ function copyMetadata(sourceEvent, additionalMetadata) {
|
|
|
474
725
|
}
|
|
475
726
|
__name(copyMetadata, "copyMetadata");
|
|
476
727
|
function mergeMetadata(...metadataObjects) {
|
|
477
|
-
|
|
728
|
+
const merged = {};
|
|
729
|
+
for (const metadata of metadataObjects) {
|
|
730
|
+
if (!metadata) continue;
|
|
731
|
+
for (const key of Reflect.ownKeys(metadata)) {
|
|
732
|
+
const descriptor = Object.getOwnPropertyDescriptor(metadata, key);
|
|
733
|
+
if (!descriptor?.enumerable) continue;
|
|
734
|
+
Object.defineProperty(merged, key, {
|
|
735
|
+
value: metadata[key],
|
|
736
|
+
writable: true,
|
|
737
|
+
enumerable: true,
|
|
738
|
+
configurable: true
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
return merged;
|
|
478
743
|
}
|
|
479
744
|
__name(mergeMetadata, "mergeMetadata");
|
|
480
745
|
|
|
@@ -510,12 +775,20 @@ var Entity = class {
|
|
|
510
775
|
* Subclasses can mutate this directly or use helper methods.
|
|
511
776
|
*/
|
|
512
777
|
_state;
|
|
778
|
+
/**
|
|
779
|
+
* **State ownership.** Plain-object and array states are shallow-copied
|
|
780
|
+
* before the freeze, so the caller's own object stays mutable. A CLASS
|
|
781
|
+
* INSTANCE passed as state is an ownership transfer: it is frozen
|
|
782
|
+
* in place (a copy would strip its prototype) — do not keep mutating
|
|
783
|
+
* the instance after handing it to the entity. The same contract
|
|
784
|
+
* applies to {@link setState}.
|
|
785
|
+
*/
|
|
513
786
|
constructor(id, initialState) {
|
|
514
787
|
if (id === null || id === void 0) {
|
|
515
788
|
throw new Error("Entity ID cannot be null or undefined");
|
|
516
789
|
}
|
|
517
790
|
this.id = id;
|
|
518
|
-
this._state = freezeShallow(initialState);
|
|
791
|
+
this._state = freezeShallow(shallowCopyOwned(initialState));
|
|
519
792
|
this.validateState(this._state);
|
|
520
793
|
}
|
|
521
794
|
/**
|
|
@@ -546,11 +819,15 @@ var Entity = class {
|
|
|
546
819
|
* This is a convenience method for state mutations.
|
|
547
820
|
* Automatically validates the newState using `validateState()`.
|
|
548
821
|
*
|
|
822
|
+
* Plain-object and array states are shallow-copied before the freeze
|
|
823
|
+
* (the caller's object stays mutable); a class-instance state is an
|
|
824
|
+
* ownership transfer and is frozen in place — see the constructor.
|
|
825
|
+
*
|
|
549
826
|
* @param newState - The new state
|
|
550
827
|
*/
|
|
551
828
|
setState(newState) {
|
|
552
829
|
this.validateState(newState);
|
|
553
|
-
this._state = freezeShallow(newState);
|
|
830
|
+
this._state = freezeShallow(shallowCopyOwned(newState));
|
|
554
831
|
}
|
|
555
832
|
};
|
|
556
833
|
function freezeShallow(value) {
|
|
@@ -560,6 +837,14 @@ function freezeShallow(value) {
|
|
|
560
837
|
return value;
|
|
561
838
|
}
|
|
562
839
|
__name(freezeShallow, "freezeShallow");
|
|
840
|
+
function shallowCopyOwned(value) {
|
|
841
|
+
if (value === null || typeof value !== "object") return value;
|
|
842
|
+
if (Array.isArray(value)) return [...value];
|
|
843
|
+
const proto = Object.getPrototypeOf(value);
|
|
844
|
+
if (proto !== Object.prototype && proto !== null) return value;
|
|
845
|
+
return Object.assign(Object.create(proto), value);
|
|
846
|
+
}
|
|
847
|
+
__name(shallowCopyOwned, "shallowCopyOwned");
|
|
563
848
|
function sameEntity(a, b) {
|
|
564
849
|
return a.id === b.id;
|
|
565
850
|
}
|
|
@@ -701,6 +986,12 @@ var BaseAggregate = class extends Entity {
|
|
|
701
986
|
* call `super.onPersisted(version)` — there is nothing in the parent
|
|
702
987
|
* implementation to preserve.
|
|
703
988
|
*
|
|
989
|
+
* **Observer contract: errors are swallowed.** `withCommit` invokes
|
|
990
|
+
* `markPersisted` after the transaction has committed; a throwing hook
|
|
991
|
+
* must neither abort the loop for peer aggregates nor make the
|
|
992
|
+
* committed write look failed, so `withCommit` catches and discards
|
|
993
|
+
* hook errors. Handle failures inside the hook if you need them.
|
|
994
|
+
*
|
|
704
995
|
* **`onPersisted` deliberately receives only the version, not the
|
|
705
996
|
* drained events.** Event-driven post-persist logic (aggregate-level
|
|
706
997
|
* audit logging, per-event-type side effects) belongs in `EventBus`
|
|
@@ -739,14 +1030,46 @@ var BaseAggregate = class extends Entity {
|
|
|
739
1030
|
* Creates a snapshot of the current aggregate state — the state at
|
|
740
1031
|
* this moment plus the version. Useful for ES snapshot policies and
|
|
741
1032
|
* for state-stored backup / restore.
|
|
1033
|
+
*
|
|
1034
|
+
* The state is converted via {@link toSnapshotState}; the default
|
|
1035
|
+
* requires plain, serialisable data and fails fast otherwise.
|
|
742
1036
|
*/
|
|
743
1037
|
createSnapshot() {
|
|
744
1038
|
return {
|
|
745
|
-
state:
|
|
1039
|
+
state: this.toSnapshotState(this._state),
|
|
746
1040
|
version: this.version,
|
|
747
1041
|
snapshotAt: /* @__PURE__ */ new Date()
|
|
748
1042
|
};
|
|
749
1043
|
}
|
|
1044
|
+
/**
|
|
1045
|
+
* Converts live aggregate state into the plain-data shape stored in a
|
|
1046
|
+
* snapshot. The default validates that the state graph is plain,
|
|
1047
|
+
* serialisable data (no class instances, functions, Promise/WeakMap/
|
|
1048
|
+
* WeakSet) and then `structuredClone`s it — class instances would
|
|
1049
|
+
* silently lose their prototype here AND on every snapshot-store
|
|
1050
|
+
* round-trip, so the default fails fast with the offending path
|
|
1051
|
+
* instead of producing a snapshot that breaks on first method call
|
|
1052
|
+
* after restore.
|
|
1053
|
+
*
|
|
1054
|
+
* Override this together with {@link fromSnapshotState} (and the
|
|
1055
|
+
* `TSnapshotState` generic) when the state carries class-based child
|
|
1056
|
+
* entities. The override owns isolation: return fresh objects, not
|
|
1057
|
+
* references into live state.
|
|
1058
|
+
*/
|
|
1059
|
+
toSnapshotState(state) {
|
|
1060
|
+
assertSnapshotSafe(state, "", /* @__PURE__ */ new WeakSet());
|
|
1061
|
+
return structuredClone(state);
|
|
1062
|
+
}
|
|
1063
|
+
/**
|
|
1064
|
+
* Converts the plain-data snapshot shape back into live aggregate
|
|
1065
|
+
* state. The default `structuredClone`s the stored state so the
|
|
1066
|
+
* restored aggregate never aliases the snapshot object. Override
|
|
1067
|
+
* together with {@link toSnapshotState} to reconstruct class-based
|
|
1068
|
+
* child entities.
|
|
1069
|
+
*/
|
|
1070
|
+
fromSnapshotState(stored) {
|
|
1071
|
+
return structuredClone(stored);
|
|
1072
|
+
}
|
|
750
1073
|
/**
|
|
751
1074
|
* Sugar for `createDomainEvent` that auto-injects `aggregateId`
|
|
752
1075
|
* (from `this.id`) and `aggregateType` (from {@link aggregateType})
|
|
@@ -789,6 +1112,77 @@ var BaseAggregate = class extends Entity {
|
|
|
789
1112
|
});
|
|
790
1113
|
}
|
|
791
1114
|
};
|
|
1115
|
+
function assertSnapshotSafe(value, path, seen) {
|
|
1116
|
+
if (typeof value === "function") {
|
|
1117
|
+
throw new Error(
|
|
1118
|
+
`createSnapshot: state${path} is a function \u2014 snapshot state must be plain, serialisable data. Override toSnapshotState()/fromSnapshotState() to map it.`
|
|
1119
|
+
);
|
|
1120
|
+
}
|
|
1121
|
+
if (value === null || typeof value !== "object") return;
|
|
1122
|
+
const obj = value;
|
|
1123
|
+
if (seen.has(obj)) return;
|
|
1124
|
+
seen.add(obj);
|
|
1125
|
+
if (Array.isArray(obj)) {
|
|
1126
|
+
for (let i = 0; i < obj.length; i++) {
|
|
1127
|
+
assertSnapshotSafe(obj[i], `${path}[${i}]`, seen);
|
|
1128
|
+
}
|
|
1129
|
+
return;
|
|
1130
|
+
}
|
|
1131
|
+
const tag = Object.prototype.toString.call(obj);
|
|
1132
|
+
if (isBuiltInObject(obj, tag)) {
|
|
1133
|
+
if (tag === "[object Map]") {
|
|
1134
|
+
let i = 0;
|
|
1135
|
+
for (const [key, entryValue] of obj) {
|
|
1136
|
+
assertSnapshotSafe(key, `${path}<map key #${i}>`, seen);
|
|
1137
|
+
assertSnapshotSafe(entryValue, `${path}<map value #${i}>`, seen);
|
|
1138
|
+
i++;
|
|
1139
|
+
}
|
|
1140
|
+
return;
|
|
1141
|
+
}
|
|
1142
|
+
if (tag === "[object Set]") {
|
|
1143
|
+
let i = 0;
|
|
1144
|
+
for (const member of obj) {
|
|
1145
|
+
assertSnapshotSafe(member, `${path}<set member #${i}>`, seen);
|
|
1146
|
+
i++;
|
|
1147
|
+
}
|
|
1148
|
+
return;
|
|
1149
|
+
}
|
|
1150
|
+
if (tag === "[object Promise]" || tag === "[object WeakMap]" || tag === "[object WeakSet]") {
|
|
1151
|
+
throw new Error(
|
|
1152
|
+
`createSnapshot: state${path} is a ${tag.slice(8, -1)} \u2014 it cannot be cloned or persisted. Override toSnapshotState()/fromSnapshotState() to map it.`
|
|
1153
|
+
);
|
|
1154
|
+
}
|
|
1155
|
+
if (tag === "[object Error]") {
|
|
1156
|
+
throw new Error(
|
|
1157
|
+
`createSnapshot: state${path} is an Error \u2014 structuredClone downgrades Error subclasses to plain Error and silently drops custom fields, so the restored value would not round-trip. Override toSnapshotState()/fromSnapshotState() to map it to plain data.`
|
|
1158
|
+
);
|
|
1159
|
+
}
|
|
1160
|
+
return;
|
|
1161
|
+
}
|
|
1162
|
+
const proto = Object.getPrototypeOf(obj);
|
|
1163
|
+
if (proto === Object.prototype || proto === null) {
|
|
1164
|
+
for (const key of Reflect.ownKeys(obj)) {
|
|
1165
|
+
const descriptor = Object.getOwnPropertyDescriptor(obj, key);
|
|
1166
|
+
if (!descriptor?.enumerable) continue;
|
|
1167
|
+
if (typeof key === "symbol") {
|
|
1168
|
+
throw new Error(
|
|
1169
|
+
`createSnapshot: state${path} has a symbol-keyed property (${String(key)}) \u2014 structuredClone silently drops symbol keys, so the snapshot would lose state. Override toSnapshotState()/fromSnapshotState() to map it.`
|
|
1170
|
+
);
|
|
1171
|
+
}
|
|
1172
|
+
assertSnapshotSafe(
|
|
1173
|
+
obj[key],
|
|
1174
|
+
`${path}.${key}`,
|
|
1175
|
+
seen
|
|
1176
|
+
);
|
|
1177
|
+
}
|
|
1178
|
+
return;
|
|
1179
|
+
}
|
|
1180
|
+
const name = proto.constructor?.name || "anonymous class";
|
|
1181
|
+
throw new Error(
|
|
1182
|
+
`createSnapshot: state${path} is a class instance (${name}) \u2014 structuredClone would strip its prototype and methods, producing a snapshot that breaks on the first method call after restore. Override toSnapshotState()/fromSnapshotState() to map child entities to plain data.`
|
|
1183
|
+
);
|
|
1184
|
+
}
|
|
1185
|
+
__name(assertSnapshotSafe, "assertSnapshotSafe");
|
|
792
1186
|
|
|
793
1187
|
// src/aggregate/aggregate-root.ts
|
|
794
1188
|
var AggregateRoot = class extends BaseAggregate {
|
|
@@ -873,9 +1267,9 @@ var AggregateRoot = class extends BaseAggregate {
|
|
|
873
1267
|
* @param snapshot - The snapshot to restore from
|
|
874
1268
|
*/
|
|
875
1269
|
restoreFromSnapshot(snapshot) {
|
|
876
|
-
const
|
|
877
|
-
this.validateState(
|
|
878
|
-
this._state = freezeShallow(
|
|
1270
|
+
const restored = this.fromSnapshotState(snapshot.state);
|
|
1271
|
+
this.validateState(restored);
|
|
1272
|
+
this._state = freezeShallow(restored);
|
|
879
1273
|
this.markRestored(snapshot.version);
|
|
880
1274
|
}
|
|
881
1275
|
};
|
|
@@ -978,7 +1372,7 @@ var EventSourcedAggregate = class extends BaseAggregate {
|
|
|
978
1372
|
*/
|
|
979
1373
|
dispatchAndCommit(event, isNew) {
|
|
980
1374
|
this.validateEvent(event);
|
|
981
|
-
const handler = this.handlers[event.type];
|
|
1375
|
+
const handler = Object.hasOwn(this.handlers, event.type) ? this.handlers[event.type] : void 0;
|
|
982
1376
|
if (!handler) {
|
|
983
1377
|
throw new MissingHandlerError(event.type);
|
|
984
1378
|
}
|
|
@@ -995,17 +1389,26 @@ var EventSourcedAggregate = class extends BaseAggregate {
|
|
|
995
1389
|
* infrastructure boundary, where event-stream corruption is an expected
|
|
996
1390
|
* recoverable failure. Unexpected (non-DomainError) throws propagate.
|
|
997
1391
|
*
|
|
1392
|
+
* All-or-nothing: if any event mid-stream throws, the aggregate's state
|
|
1393
|
+
* is rolled back to its pre-call value — same contract as
|
|
1394
|
+
* `restoreFromSnapshotWithEvents`. Partial replay is never observable.
|
|
1395
|
+
* (Version needs no rollback: replay dispatches with `isNew = false`,
|
|
1396
|
+
* which never bumps it; only the final `markRestored` advances it.)
|
|
1397
|
+
*
|
|
998
1398
|
* Version advances additively: the aggregate's pre-existing version plus
|
|
999
1399
|
* `history.length`. A fresh aggregate (v=0) loading 3 events ends at v=3;
|
|
1000
1400
|
* an aggregate already at v=1 (e.g. after a creation event) loading
|
|
1001
1401
|
* 2 events ends at v=3, not v=2.
|
|
1002
1402
|
*/
|
|
1003
1403
|
loadFromHistory(history) {
|
|
1404
|
+
if (history.length === 0) return ok();
|
|
1405
|
+
const previousState = this._state;
|
|
1004
1406
|
const startVersion = this.version;
|
|
1005
1407
|
for (const event of history) {
|
|
1006
1408
|
try {
|
|
1007
1409
|
this.dispatchAndCommit(event, false);
|
|
1008
1410
|
} catch (e) {
|
|
1411
|
+
this._state = previousState;
|
|
1009
1412
|
if (e instanceof DomainError) return err(e);
|
|
1010
1413
|
throw e;
|
|
1011
1414
|
}
|
|
@@ -1026,7 +1429,7 @@ var EventSourcedAggregate = class extends BaseAggregate {
|
|
|
1026
1429
|
restoreFromSnapshotWithEvents(snapshot, eventsAfterSnapshot) {
|
|
1027
1430
|
const previousState = this._state;
|
|
1028
1431
|
const previousVersion = this.version;
|
|
1029
|
-
this._state = freezeShallow(
|
|
1432
|
+
this._state = freezeShallow(this.fromSnapshotState(snapshot.state));
|
|
1030
1433
|
this.setVersion(snapshot.version);
|
|
1031
1434
|
for (const event of eventsAfterSnapshot) {
|
|
1032
1435
|
try {
|
|
@@ -1044,12 +1447,31 @@ var EventSourcedAggregate = class extends BaseAggregate {
|
|
|
1044
1447
|
return ok();
|
|
1045
1448
|
}
|
|
1046
1449
|
};
|
|
1450
|
+
|
|
1451
|
+
// src/app/describe-thrown.ts
|
|
1452
|
+
function describeThrown(error) {
|
|
1453
|
+
if (error instanceof Error) return error.message;
|
|
1454
|
+
try {
|
|
1455
|
+
const json = JSON.stringify(error);
|
|
1456
|
+
if (json !== void 0) return json;
|
|
1457
|
+
} catch {
|
|
1458
|
+
}
|
|
1459
|
+
return String(error);
|
|
1460
|
+
}
|
|
1461
|
+
__name(describeThrown, "describeThrown");
|
|
1462
|
+
|
|
1463
|
+
// src/app/command-bus.ts
|
|
1047
1464
|
var CommandBus = class {
|
|
1048
1465
|
static {
|
|
1049
1466
|
__name(this, "CommandBus");
|
|
1050
1467
|
}
|
|
1051
1468
|
handlers = /* @__PURE__ */ new Map();
|
|
1052
1469
|
register(commandType, handler) {
|
|
1470
|
+
if (this.handlers.has(commandType)) {
|
|
1471
|
+
throw new Error(
|
|
1472
|
+
`CommandBus: a handler for command type "${commandType}" is already registered`
|
|
1473
|
+
);
|
|
1474
|
+
}
|
|
1053
1475
|
this.handlers.set(commandType, (cmd) => handler(cmd));
|
|
1054
1476
|
}
|
|
1055
1477
|
async execute(command) {
|
|
@@ -1060,9 +1482,7 @@ var CommandBus = class {
|
|
|
1060
1482
|
try {
|
|
1061
1483
|
return await handler(command);
|
|
1062
1484
|
} catch (error) {
|
|
1063
|
-
return err(
|
|
1064
|
-
error instanceof Error ? error.message : String(error)
|
|
1065
|
-
);
|
|
1485
|
+
return err(describeThrown(error));
|
|
1066
1486
|
}
|
|
1067
1487
|
}
|
|
1068
1488
|
};
|
|
@@ -1095,10 +1515,20 @@ async function withCommit(deps, fn) {
|
|
|
1095
1515
|
}
|
|
1096
1516
|
);
|
|
1097
1517
|
for (const agg of aggregates) {
|
|
1098
|
-
|
|
1518
|
+
try {
|
|
1519
|
+
agg.markPersisted(agg.version);
|
|
1520
|
+
} catch {
|
|
1521
|
+
}
|
|
1099
1522
|
}
|
|
1100
1523
|
if (deps.bus && events.length > 0) {
|
|
1101
|
-
|
|
1524
|
+
try {
|
|
1525
|
+
await deps.bus.publish(events);
|
|
1526
|
+
} catch (error) {
|
|
1527
|
+
try {
|
|
1528
|
+
deps.onPublishError?.(error, events);
|
|
1529
|
+
} catch {
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1102
1532
|
}
|
|
1103
1533
|
return result;
|
|
1104
1534
|
}
|
|
@@ -1109,6 +1539,11 @@ var QueryBus = class {
|
|
|
1109
1539
|
}
|
|
1110
1540
|
handlers = /* @__PURE__ */ new Map();
|
|
1111
1541
|
register(queryType, handler) {
|
|
1542
|
+
if (this.handlers.has(queryType)) {
|
|
1543
|
+
throw new Error(
|
|
1544
|
+
`QueryBus: a handler for query type "${queryType}" is already registered`
|
|
1545
|
+
);
|
|
1546
|
+
}
|
|
1112
1547
|
this.handlers.set(queryType, (query) => handler(query));
|
|
1113
1548
|
}
|
|
1114
1549
|
async execute(query) {
|
|
@@ -1120,9 +1555,7 @@ var QueryBus = class {
|
|
|
1120
1555
|
const result = await handler(query);
|
|
1121
1556
|
return ok(result);
|
|
1122
1557
|
} catch (error) {
|
|
1123
|
-
return err(
|
|
1124
|
-
error instanceof Error ? error.message : String(error)
|
|
1125
|
-
);
|
|
1558
|
+
return err(describeThrown(error));
|
|
1126
1559
|
}
|
|
1127
1560
|
}
|
|
1128
1561
|
async executeUnsafe(query) {
|
|
@@ -1218,12 +1651,19 @@ var EventBusImpl = class {
|
|
|
1218
1651
|
const handlersForType = this.handlers.get(event.type);
|
|
1219
1652
|
if (handlersForType) {
|
|
1220
1653
|
const results = await Promise.allSettled(
|
|
1221
|
-
handlersForType.slice().map((handler) => handler(event))
|
|
1654
|
+
handlersForType.slice().map(async (handler) => handler(event))
|
|
1222
1655
|
);
|
|
1223
1656
|
for (const result of results) {
|
|
1224
1657
|
if (result.status === "rejected") {
|
|
1225
1658
|
errors.push(
|
|
1226
|
-
result.reason instanceof Error ? result.reason :
|
|
1659
|
+
result.reason instanceof Error ? result.reason : (
|
|
1660
|
+
// Attach the raw reason as cause — a handler
|
|
1661
|
+
// rejecting with a structured payload must stay
|
|
1662
|
+
// diagnosable, not collapse to '[object Object]'.
|
|
1663
|
+
new Error(String(result.reason), {
|
|
1664
|
+
cause: result.reason
|
|
1665
|
+
})
|
|
1666
|
+
)
|
|
1227
1667
|
);
|
|
1228
1668
|
}
|
|
1229
1669
|
}
|
|
@@ -1254,7 +1694,8 @@ var InMemoryOutbox = class {
|
|
|
1254
1694
|
}
|
|
1255
1695
|
async getPending(limit) {
|
|
1256
1696
|
const all = [...this.pending.values()];
|
|
1257
|
-
|
|
1697
|
+
if (typeof limit !== "number") return all;
|
|
1698
|
+
return all.slice(0, Math.max(0, limit));
|
|
1258
1699
|
}
|
|
1259
1700
|
async markDispatched(dispatchIds) {
|
|
1260
1701
|
for (const id of dispatchIds) this.pending.delete(id);
|