@shirudo/ddd-kit 2.2.0 → 3.0.0-rc.11

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.
@@ -0,0 +1,1304 @@
1
+ import { StructuredError } from "@shirudo/base-error";
2
+
3
+ //#region src/errors/kit-errors.ts
4
+ /** The keys `StructuredError` owns, which a declared field never replaces. */
5
+ const ENVELOPE_FIELDS = /* @__PURE__ */ new Set(["_tag", "details"]);
6
+ /**
7
+ * The fields a kit error declares of its own, for the raw log object.
8
+ *
9
+ * Every field of a kit error is an own enumerable property, so the log object
10
+ * carries them without each class repeating them.
11
+ */
12
+ function ownFields(error) {
13
+ const fields = {};
14
+ for (const key of Object.keys(error)) {
15
+ if (ENVELOPE_FIELDS.has(key)) continue;
16
+ try {
17
+ const safe = logSafeValue(error[key]);
18
+ if (safe !== void 0) fields[key] = safe;
19
+ } catch {}
20
+ }
21
+ return fields;
22
+ }
23
+ /**
24
+ * Projects one field value to something a log serializer survives.
25
+ *
26
+ * A field of type `unknown` can hold a driver value with a cycle, a bigint or
27
+ * a symbol, and `JSON.stringify` throws on the first two. A serializer that
28
+ * throws inside the failure path costs the whole report, so a value that does
29
+ * not survive the attempt is left out. An error keeps its name, message and
30
+ * code, which is what a reader needs and cannot cycle.
31
+ */
32
+ function logSafeValue(value) {
33
+ switch (typeof value) {
34
+ case "string":
35
+ case "number":
36
+ case "boolean": return value;
37
+ case "bigint":
38
+ case "symbol": return String(value);
39
+ case "undefined":
40
+ case "function": return;
41
+ }
42
+ if (value === null) return null;
43
+ if (value instanceof Error) {
44
+ const code = value.code;
45
+ return {
46
+ name: String(value.name),
47
+ message: String(value.message),
48
+ ...typeof code === "string" ? { code } : {}
49
+ };
50
+ }
51
+ try {
52
+ JSON.stringify(value);
53
+ return value;
54
+ } catch {
55
+ return;
56
+ }
57
+ }
58
+ /**
59
+ * Abstract base for **domain-invariant violations**. Domain methods
60
+ * (aggregates, entity validation hooks, value-object constructors)
61
+ * throw `DomainError`-derived exceptions when a business rule is
62
+ * violated. Consumers derive their own concrete errors (e.g.
63
+ * `class OrderAlreadyShippedError extends DomainError<"ORDER_ALREADY_SHIPPED">`)
64
+ * for `instanceof`-style catching at the App-Service boundary, where
65
+ * they typically map to HTTP 400 / business-rule responses.
66
+ *
67
+ * The library itself ships no business-rule `DomainError` subclass: the
68
+ * kit can't know your invariants. (The domain-state-machine module's
69
+ * transition errors are the structural exception.)
70
+ *
71
+ * The `category` is fixed to `"DOMAIN"` and `retryable` defaults to
72
+ * `false`, so a subclass supplies only its `code` and `message`:
73
+ *
74
+ * ```ts
75
+ * class OrderAlreadyShippedError extends DomainError<"ORDER_ALREADY_SHIPPED"> {
76
+ * constructor(orderId: string) {
77
+ * super({
78
+ * code: "ORDER_ALREADY_SHIPPED",
79
+ * message: `Order ${orderId} has already been shipped`,
80
+ * });
81
+ * }
82
+ * }
83
+ * ```
84
+ */
85
+ var DomainError = class extends StructuredError {
86
+ constructor(options) {
87
+ super({
88
+ code: options.code,
89
+ category: "DOMAIN",
90
+ retryable: options.retryable ?? false,
91
+ message: options.message,
92
+ cause: options.cause
93
+ });
94
+ }
95
+ /** Carries the fields the concrete error declares into the log object. */
96
+ buildLogObject() {
97
+ return {
98
+ ...ownFields(this),
99
+ ...super.buildLogObject()
100
+ };
101
+ }
102
+ };
103
+ /**
104
+ * Internal base for the kit's crash-loud **WIRING** family: deterministic
105
+ * programming/configuration bugs that must fail the operation loudly and
106
+ * never be absorbed by generic domain or infrastructure handlers. One
107
+ * implementation of the `{ category: "WIRING", retryable: false }` shape
108
+ * so the family cannot drift. Exported for the kit's own modules only;
109
+ * not part of the package entries.
110
+ */
111
+ var KitWiringError = class extends StructuredError {
112
+ constructor(code, message, cause) {
113
+ super({
114
+ code,
115
+ category: "WIRING",
116
+ retryable: false,
117
+ message,
118
+ cause
119
+ });
120
+ }
121
+ /** Carries the fields the concrete error declares into the log object. */
122
+ buildLogObject() {
123
+ return {
124
+ ...ownFields(this),
125
+ ...super.buildLogObject()
126
+ };
127
+ }
128
+ };
129
+ /**
130
+ * Abstract base for **infrastructure / persistence failures** that the
131
+ * App-Service can recover from: typically by retrying, by returning
132
+ * HTTP 404 / 409, or by surfacing a "please try again" UX. These are
133
+ * not domain-invariant violations (the business rules were not
134
+ * broken); they describe race conditions and missing rows at the
135
+ * storage boundary.
136
+ *
137
+ * The `category` is fixed to `"INFRASTRUCTURE"`; `retryable` defaults
138
+ * to `false` (opt in per subclass, see {@link ConcurrencyConflictError}).
139
+ *
140
+ * Library-internal concrete subclasses: {@link AggregateNotFoundError},
141
+ * {@link ConcurrencyConflictError}, {@link DuplicateAggregateError},
142
+ * plus the unit-of-work lifecycle wrappers `CommitError` and
143
+ * `RollbackError` (in `src/application/unit-of-work/errors.ts`).
144
+ */
145
+ var InfrastructureError = class extends StructuredError {
146
+ constructor(options) {
147
+ super({
148
+ code: options.code,
149
+ category: "INFRASTRUCTURE",
150
+ retryable: options.retryable ?? false,
151
+ message: options.message,
152
+ cause: options.cause
153
+ });
154
+ }
155
+ /** Carries the fields the concrete error declares into the log object. */
156
+ buildLogObject() {
157
+ return {
158
+ ...ownFields(this),
159
+ ...super.buildLogObject()
160
+ };
161
+ }
162
+ };
163
+ /**
164
+ * Copy-safe membership check for the kit's domain-error family.
165
+ *
166
+ * `instanceof` is false for an error constructed by another loaded copy of
167
+ * the kit (a separately installed adapter package, a CJS/ESM dual load), so
168
+ * kit boundaries that route by error family fall back to the structural
169
+ * `category` field, the stable cross-copy contract.
170
+ */
171
+ function isDomainErrorLike(value) {
172
+ return value instanceof DomainError || value instanceof Error && value.category === "DOMAIN";
173
+ }
174
+ /**
175
+ * Copy-safe membership check for the kit's infrastructure-error family.
176
+ * Same rationale as {@link isDomainErrorLike}.
177
+ */
178
+ function isInfrastructureErrorLike(value) {
179
+ return value instanceof InfrastructureError || value instanceof Error && value.category === "INFRASTRUCTURE";
180
+ }
181
+ /**
182
+ * Copy-safe membership check for the kit's wiring-error family.
183
+ * Same rationale as {@link isDomainErrorLike}.
184
+ *
185
+ * A wiring error states a deterministic programming or configuration defect.
186
+ * A kit boundary that translates failures uses this check to pass such an
187
+ * error through untouched, instead of relabelling it as a store failure.
188
+ *
189
+ * The check narrows to the structural shape, not to a class: the family's
190
+ * base stays kit-internal, and an error from another kit copy carries the
191
+ * shape without being an instance of this copy's class.
192
+ */
193
+ function isWiringErrorLike(value) {
194
+ return value instanceof KitWiringError || value instanceof Error && value.category === "WIRING";
195
+ }
196
+ /**
197
+ * A finite-capacity in-memory reference adapter rejected new state before
198
+ * mutation. Existing records remain usable; callers must release explicit
199
+ * lifecycle state, increase the configured limit, or switch to a durable
200
+ * adapter. The error is not retryable without one of those external changes.
201
+ */
202
+ var InMemoryCapacityExceededError = class extends InfrastructureError {
203
+ store;
204
+ resource;
205
+ limit;
206
+ current;
207
+ attempted;
208
+ constructor(options) {
209
+ super({
210
+ code: "IN_MEMORY_CAPACITY_EXCEEDED",
211
+ message: `${options.store} cannot retain ${options.attempted} new ${options.resource}: configured limit ${options.limit}, currently retained ${options.current}`
212
+ });
213
+ this.store = options.store;
214
+ this.resource = options.resource;
215
+ this.limit = options.limit;
216
+ this.current = options.current;
217
+ this.attempted = options.attempted;
218
+ }
219
+ };
220
+ /**
221
+ * Thrown when a projection built with `projectionFromHandlers` receives an
222
+ * event type with no own handler entry: the declared event union and the
223
+ * handler map disagree at runtime, which is a programming / configuration
224
+ * bug rather than a domain or infrastructure failure.
225
+ *
226
+ * Deliberately **not** on `DomainError` or `InfrastructureError`:
227
+ * a generic `catch (e instanceof DomainError)` handler at the App
228
+ * layer must not mask a forgotten handler; this should crash loud and
229
+ * fail the calling Use Case so the bug surfaces in development.
230
+ *
231
+ * Use `isBaseError(e)` from `@shirudo/base-error` to detect
232
+ * "any structured error from the kit or any other BaseError-using
233
+ * library" at the App boundary.
234
+ */
235
+ var MissingHandlerError = class extends KitWiringError {
236
+ eventType;
237
+ constructor(eventType, cause) {
238
+ super("MISSING_HANDLER", `Missing handler for event type: ${eventType}`, cause);
239
+ this.eventType = eventType;
240
+ }
241
+ };
242
+ /**
243
+ * Thrown by an event-sourced aggregate when `apply()` or replay reaches an
244
+ * event type with no own entry in the `folds` map: the declared event union
245
+ * and the map disagree at runtime. Same posture as
246
+ * {@link MissingHandlerError}: a deterministic bug, never a domain
247
+ * rejection, so it propagates through `replayHistory` instead of riding its
248
+ * `Result`.
249
+ */
250
+ var MissingFoldError = class extends KitWiringError {
251
+ eventType;
252
+ constructor(eventType, cause) {
253
+ super("MISSING_FOLD", `Missing fold for event type: ${eventType}`, cause);
254
+ this.eventType = eventType;
255
+ }
256
+ };
257
+ /**
258
+ * Thrown by an event-sourced aggregate when a fold returns `undefined`
259
+ * for an event, which is almost always a fold without a `return` statement.
260
+ * Storing that result would set the aggregate state to `undefined`, record
261
+ * the fact anyway on the apply path, and leave every later fold working on
262
+ * nothing. Same posture as {@link MissingFoldError}: a deterministic bug
263
+ * in the folds map, never a domain rejection, so it propagates through
264
+ * `replayHistory` instead of riding its `Result`.
265
+ */
266
+ var FoldReturnedNoStateError = class extends KitWiringError {
267
+ eventType;
268
+ constructor(eventType) {
269
+ super("FOLD_RETURNED_NO_STATE", `The fold for event type "${eventType}" returned no state. A fold must return the next state; check for a missing return statement.`);
270
+ this.eventType = eventType;
271
+ }
272
+ };
273
+ /**
274
+ * Thrown by `EventSourcedAggregate.setState`: on an event-sourced aggregate
275
+ * the state changes only through `apply()`, where the fact is recorded and
276
+ * the version advances with it. A direct state write would leave the
277
+ * instance ahead of its stream with nothing to replay. A wiring error: a
278
+ * deterministic bug in the aggregate's own code, the remedy is an event
279
+ * and a handler.
280
+ */
281
+ var DirectStateMutationError = class extends KitWiringError {
282
+ identity;
283
+ constructor(options) {
284
+ super("DIRECT_STATE_MUTATION", `Aggregate ${describeAggregateIdentity(options.identity)} is event-sourced: its state changes only through apply(). Record the fact as an event and fold it in a handler instead of calling setState.`);
285
+ this.identity = detachAggregateIdentity(options.identity);
286
+ }
287
+ };
288
+ /**
289
+ * Thrown by `Projector.project` when an event cannot be projected
290
+ * safely because its cursor is missing or malformed, or its aggregate
291
+ * identity is absent. Applying such an event would break idempotency, so
292
+ * the batch fails. Events written by `withCommit` carry the complete
293
+ * cursor automatically; other sources compose a gap-proof committed-event
294
+ * envelope. A well-formed cursor that does not continue the stored chain
295
+ * instead throws {@link ProjectionGapError}.
296
+ *
297
+ * A wiring error, not a `DomainError`: see {@link MissingHandlerError}
298
+ * for the rationale of crashing loud at the App layer.
299
+ */
300
+ var UnprojectableEventError = class extends KitWiringError {
301
+ projection;
302
+ eventId;
303
+ constructor(projection, eventId, reason, cause) {
304
+ super("UNPROJECTABLE_EVENT", `Projector(${projection}): event ${eventId} ${reason}`, cause);
305
+ this.projection = projection;
306
+ this.eventId = eventId;
307
+ }
308
+ };
309
+ /**
310
+ * Thrown when a valid projection cursor does not continue the stored
311
+ * per-aggregate chain. This is an infrastructure/delivery failure: an
312
+ * event or commit is missing, commonly because a partition reordered or
313
+ * dead-lettered it. The projector does not apply the later event and the
314
+ * checkpoint stays put until the missing history is replayed or the
315
+ * projection is rebuilt.
316
+ */
317
+ var ProjectionGapError = class extends InfrastructureError {
318
+ projection;
319
+ eventId;
320
+ previousPosition;
321
+ receivedPosition;
322
+ constructor(projection, eventId, previousPosition, receivedPosition) {
323
+ super({
324
+ code: "PROJECTION_GAP",
325
+ message: `Projector(${projection}): event ${eventId} creates a projection gap after ${previousPosition}; received ${receivedPosition}. Replay the missing commit before advancing the checkpoint.`
326
+ });
327
+ this.projection = projection;
328
+ this.eventId = eventId;
329
+ this.previousPosition = previousPosition;
330
+ this.receivedPosition = receivedPosition;
331
+ }
332
+ };
333
+ /**
334
+ * Thrown when one batch delivers previously unseen positions of the same
335
+ * aggregate in descending order. Unlike {@link ProjectionGapError}, this is
336
+ * direct proof that the feed violated its per-aggregate ordering contract;
337
+ * no missing-history inference is needed. Positions already covered by the
338
+ * checkpoint at batch start and exact receipts repeated inside the batch
339
+ * remain valid redeliveries and do not trip this diagnostic guard.
340
+ */
341
+ var ProjectionOrderViolationError = class extends InfrastructureError {
342
+ projection;
343
+ eventId;
344
+ previousReceivedPosition;
345
+ receivedPosition;
346
+ constructor(projection, eventId, previousReceivedPosition, receivedPosition) {
347
+ super({
348
+ code: "PROJECTION_ORDER_VIOLATION",
349
+ message: `Projector(${projection}): event ${eventId} at ${receivedPosition} arrived after the later unprocessed position ${previousReceivedPosition} in the same batch. Partition or serialize the feed by aggregate source.`
350
+ });
351
+ this.projection = projection;
352
+ this.eventId = eventId;
353
+ this.previousReceivedPosition = previousReceivedPosition;
354
+ this.receivedPosition = receivedPosition;
355
+ }
356
+ };
357
+ /**
358
+ * Thrown when a source maps different event identities to one position, either
359
+ * inside the current batch or at the position stored as the projection's
360
+ * watermark. The checkpoint retains the identity of that one last-applied
361
+ * event, so the durable collision is provable without keeping an unbounded
362
+ * processed-event ledger. Positions behind the watermark remain governed by
363
+ * the source's one-logical-event-per-position contract.
364
+ */
365
+ var ProjectionIdentityViolationError = class extends InfrastructureError {
366
+ projection;
367
+ eventId;
368
+ recordedEventId;
369
+ position;
370
+ constructor(projection, eventId, recordedEventId, position) {
371
+ super({
372
+ code: "PROJECTION_IDENTITY_VIOLATION",
373
+ message: `Projector(${projection}): position ${position} was already associated with event ${recordedEventId}, but the source supplied event ${eventId} at the same position. A source must map exactly one logical event to each position.`
374
+ });
375
+ this.projection = projection;
376
+ this.eventId = eventId;
377
+ this.recordedEventId = recordedEventId;
378
+ this.position = position;
379
+ }
380
+ };
381
+ /**
382
+ * Thrown when one logical projection position keeps its event identity but its
383
+ * commit-boundary receipt changes. `commitSize` and
384
+ * `previousEventfulAggregateVersion` are part of the continuity proof, so a
385
+ * source must keep them immutable just like the eventId. Accepting a
386
+ * contradictory redelivery could hide an incomplete commit or predecessor.
387
+ */
388
+ var ProjectionReceiptViolationError = class extends InfrastructureError {
389
+ projection;
390
+ eventId;
391
+ recordedReceipt;
392
+ receivedReceipt;
393
+ constructor(projection, eventId, recordedReceipt, receivedReceipt) {
394
+ super({
395
+ code: "PROJECTION_RECEIPT_VIOLATION",
396
+ message: `Projector(${projection}): event ${eventId} changed its commit receipt at one logical position from ${recordedReceipt} to ${receivedReceipt}. A source must keep commitSize and previousEventfulAggregateVersion immutable.`
397
+ });
398
+ this.projection = projection;
399
+ this.eventId = eventId;
400
+ this.recordedReceipt = recordedReceipt;
401
+ this.receivedReceipt = receivedReceipt;
402
+ }
403
+ };
404
+ /** A malformed or non-JSON-safe message at an integration boundary. */
405
+ var InvalidIntegrationMessageError = class extends InfrastructureError {
406
+ path;
407
+ reason;
408
+ constructor(path, reason, cause) {
409
+ super({
410
+ code: "INVALID_INTEGRATION_MESSAGE",
411
+ message: `Invalid integration message at ${path}: ${reason}`,
412
+ cause
413
+ });
414
+ this.path = path;
415
+ this.reason = reason;
416
+ }
417
+ };
418
+ /** A malformed or non-JSON-safe command selected for durable delivery. */
419
+ var InvalidCommandMessageError = class extends InfrastructureError {
420
+ path;
421
+ reason;
422
+ constructor(path, reason, cause) {
423
+ super({
424
+ code: "INVALID_COMMAND_MESSAGE",
425
+ message: `Invalid command message at ${path}: ${reason}`,
426
+ cause
427
+ });
428
+ this.path = path;
429
+ this.reason = reason;
430
+ }
431
+ };
432
+ /**
433
+ * Thrown by `Entity` (constructor and `setState`), by the event-sourced
434
+ * fold (`apply` and replay), by the event constructors for the payload,
435
+ * and by the event metadata helpers (`createDomainEvent`'s
436
+ * `options.metadata`, `mergeMetadata`, `copyMetadata`) when the value
437
+ * carries an own `"__proto__"` data key:
438
+ * the shape `JSON.parse` produces for hostile DB rows or request bodies
439
+ * handed to reconstitute factories. Such a key can never be legitimate
440
+ * domain state; accepting it would hand a prototype-pollution payload to
441
+ * every downstream consumer that copies the state through `[[Set]]`
442
+ * (`Object.assign`, for-in assignment loops), and dropping it would be
443
+ * silent data mutation. The check looks at the root object only; nested
444
+ * objects are not walked, and a class instance is an ownership transfer
445
+ * that passes.
446
+ *
447
+ * Deliberately **not** a `DomainError` or `InfrastructureError` (same
448
+ * posture as {@link MissingHandlerError}): untrusted input reaching the
449
+ * domain layer unvalidated is a boundary bug, and a generic
450
+ * business-rule handler must not absorb it. Validate and strip untrusted
451
+ * input at the application edge; model genuinely arbitrary keys with a
452
+ * `Map`, not a plain object.
453
+ */
454
+ var HostileStateKeyError = class extends KitWiringError {
455
+ key;
456
+ constructor(key, subject = "Entity state") {
457
+ super("HOSTILE_STATE_KEY", `${subject} carries a hostile own "${key}" key, which can never be legitimate domain data. Validate and strip untrusted input at the boundary, or model arbitrary keys with a Map.`);
458
+ this.key = key;
459
+ }
460
+ };
461
+ /**
462
+ * Thrown by the `Entity` constructor when the id is not a non-blank
463
+ * string. That covers `null`, `undefined`, a blank string, and a
464
+ * non-string value that reached the constructor through a cast. An
465
+ * entity without a usable identity cannot be tracked, compared, or
466
+ * persisted, so the construction fails before any state is stored. A
467
+ * wiring error: a deterministic bug at the call site, never a domain
468
+ * rejection.
469
+ */
470
+ var MissingEntityIdError = class extends KitWiringError {
471
+ constructor(received) {
472
+ super("MISSING_ENTITY_ID", `Entity ID must be a non-blank string; received ${describeRejectedId(received)}.`);
473
+ }
474
+ };
475
+ function describeRejectedId(value) {
476
+ if (typeof value === "string") return JSON.stringify(value);
477
+ if (value === null) return "null";
478
+ if (value === void 0) return "undefined";
479
+ if (typeof value === "object") return Array.isArray(value) ? "array" : "object";
480
+ if (typeof value === "function") return "function";
481
+ return `${typeof value} ${String(value)}`;
482
+ }
483
+ /**
484
+ * Thrown when a number that is not a valid aggregate version reaches the
485
+ * kit: `toVersion`, `markReconstituted`, `setVersion`, and the post-commit
486
+ * acknowledgement all reject it. A version is a safe integer of at least
487
+ * zero, and a restore never moves below the current version. A wiring
488
+ * error: an adapter passed a corrupt row value or a wrong number, and
489
+ * the optimistic-concurrency cursor must not carry it. Not retryable.
490
+ */
491
+ var InvalidVersionError = class extends KitWiringError {
492
+ value;
493
+ reason;
494
+ constructor(value, reason) {
495
+ super("INVALID_VERSION", `Version ${String(value)} ${reason}. A version is a safe integer of at least zero; create one with toVersion(n) from the stored row value.`);
496
+ this.value = value;
497
+ this.reason = reason;
498
+ }
499
+ };
500
+ /**
501
+ * Thrown by `EventSourcedAggregate.replayHistory` when the replay target
502
+ * carries unflushed `pendingEvents`. Replaying persisted facts onto that
503
+ * instance would advance the version underneath decisions made against an
504
+ * older state and could later claim history the stream does not carry.
505
+ *
506
+ * Deliberately **not** a `DomainError` or `InfrastructureError` (same
507
+ * posture as {@link MissingHandlerError}): a deterministic programming
508
+ * bug in how the aggregate was constructed before the restore. It
509
+ * propagates as a throw instead of riding the replay methods' `Result`
510
+ * channel, so a generic corrupted-stream handler cannot absorb it.
511
+ * Reconstitution belongs on a bare instance: construct the aggregate
512
+ * without factory-recorded events or prior mutations, then restore.
513
+ *
514
+ * Each throw site carries the safe remedy in its `reason`. Persistence
515
+ * lifecycle state is intentionally not mutable through the aggregate API:
516
+ * commit an actually saved instance through application orchestration, or
517
+ * discard a dirty instance and replay into a fresh one.
518
+ */
519
+ var UnreplayableAggregateError = class extends KitWiringError {
520
+ identity;
521
+ constructor(options) {
522
+ super("UNREPLAYABLE_AGGREGATE", `Cannot replay onto aggregate ${describeAggregateIdentity(options.identity)}: ${options.reason}. Reconstitute on a fresh instance (no factory-recorded events, no unpersisted mutations).`);
523
+ this.identity = detachAggregateIdentity(options.identity);
524
+ }
525
+ };
526
+ /**
527
+ * Renders an aggregate identity for a kit message as `Type(id)`, the one
528
+ * format every kit message uses, so a log search finds all of them. Kit
529
+ * modules only; not part of the package entries.
530
+ */
531
+ function describeAggregateIdentity(identity) {
532
+ return `${identity.aggregateType}(${identity.aggregateId})`;
533
+ }
534
+ /**
535
+ * A frozen copy of the two aggregate identity fields. An error keeps its own
536
+ * copy, so it neither shares the caller's object nor carries its other
537
+ * properties into the log. Kit modules only; not part of the package
538
+ * entries.
539
+ */
540
+ function detachAggregateIdentity(identity) {
541
+ return Object.freeze({
542
+ aggregateType: identity.aggregateType,
543
+ aggregateId: identity.aggregateId
544
+ });
545
+ }
546
+ function detachPartialAggregateIdentity(identity) {
547
+ return Object.freeze({
548
+ ...identity.aggregateType === void 0 ? {} : { aggregateType: identity.aggregateType },
549
+ ...identity.aggregateId === void 0 ? {} : { aggregateId: identity.aggregateId }
550
+ });
551
+ }
552
+ /** The identity the event names; a missing field falls back to the receiving aggregate. */
553
+ function describeEventIdentity(options) {
554
+ const { expected, actual } = options;
555
+ return describeAggregateIdentity({
556
+ aggregateType: actual.aggregateType ?? expected.aggregateType,
557
+ aggregateId: actual.aggregateId ?? expected.aggregateId
558
+ });
559
+ }
560
+ /**
561
+ * Thrown by `EventSourcedAggregate.apply()` when a NEW event carries an
562
+ * `aggregateId` or `aggregateType` naming a different aggregate: a
563
+ * deterministic programming bug at the call site (a hand-built or
564
+ * copied event that belongs elsewhere), caught before the event can be
565
+ * recorded and poison the own stream. Events with MISSING aggregate
566
+ * identity fields do not trip this: `apply()` stamps them from the aggregate,
567
+ * the same guarantee `createEvent` gives. A wiring error, distinct
568
+ * from {@link ForeignEventError} on purpose: a wrong new event is a
569
+ * bug in today's code, a wrong PERSISTED row is corrupted or miswired
570
+ * infrastructure, and handlers for one must not absorb the other.
571
+ */
572
+ var MisattributedEventError = class extends KitWiringError {
573
+ /** Identity of the aggregate that received the event. */
574
+ expected;
575
+ /** Aggregate identity fields the event carries. */
576
+ actual;
577
+ eventType;
578
+ constructor(options) {
579
+ super("MISATTRIBUTED_EVENT", `New event "${options.eventType}" belongs to ${describeEventIdentity(options)} but was applied on ${describeAggregateIdentity(options.expected)}: fix the call site (createEvent stamps the right identity).`);
580
+ this.expected = detachAggregateIdentity(options.expected);
581
+ this.actual = detachPartialAggregateIdentity(options.actual);
582
+ this.eventType = options.eventType;
583
+ }
584
+ };
585
+ /**
586
+ * Thrown by `reconstituteAggregateFromSnapshot` when the `reconstitute`
587
+ * factory returns an aggregate at a version other than the snapshot
588
+ * version. The factory ignored the version parameter, usually a forgotten
589
+ * `markReconstituted(version)`. A wiring error in the snapshot model,
590
+ * never snapshot corruption: routing it into the discard-and-refold
591
+ * channel would mask it as perpetual silent refolding.
592
+ */
593
+ var SnapshotVersionNotRestoredError = class extends KitWiringError {
594
+ identity;
595
+ snapshotVersion;
596
+ restoredVersion;
597
+ constructor(options) {
598
+ super("SNAPSHOT_VERSION_NOT_RESTORED", `SnapshotModel.reconstitute for ${describeAggregateIdentity(options.identity)} returned an aggregate at version ${options.restoredVersion} for a snapshot at version ${options.snapshotVersion}. Reconstitution must restore the persisted version; call markReconstituted(version) inside the aggregate factory.`);
599
+ this.identity = detachAggregateIdentity(options.identity);
600
+ this.snapshotVersion = options.snapshotVersion;
601
+ this.restoredVersion = options.restoredVersion;
602
+ }
603
+ };
604
+ /**
605
+ * The structural-integrity rejection for a stored snapshot. A consumer's
606
+ * adapter-owned `SnapshotModel` may throw it from migration or reconstitution
607
+ * when the blob could not have been produced by any version of the model
608
+ * (missing fields, impossible types, truncated data). An
609
+ * `InfrastructureError`, because corrupted persistence is a storage
610
+ * problem, never a business rejection; it is nevertheless RECOVERABLE
611
+ * by design: the repository catches it, discards the derived snapshot, and
612
+ * refolds from the authoritative event stream.
613
+ */
614
+ var SnapshotCorruptedError = class extends InfrastructureError {
615
+ constructor(message, cause) {
616
+ super({
617
+ code: "SNAPSHOT_CORRUPTED",
618
+ message,
619
+ cause
620
+ });
621
+ }
622
+ };
623
+ /**
624
+ * Thrown when an event reaches the aggregate's recording paths
625
+ * (`apply`, `setState`, `addDomainEvent`) without having been minted by
626
+ * the kit's constructors: `createDomainEvent`,
627
+ * `createDomainEventFromFacts`, `createUncommittedDomainEvent`, or the
628
+ * aggregate `createEvent` helper. Those constructors deep-freeze the
629
+ * event, defensively copy payload and metadata, and mark the result as
630
+ * minted. The mark has two tiers: a
631
+ * module-private one for events of this loaded copy of the kit, and a
632
+ * cooperative `Symbol.for` brand that a second loaded copy stamps and
633
+ * recognizes. Anything else (a hand-rolled literal, a shallow-frozen
634
+ * copy with mutable nested data) is rejected: a mutable event recorded
635
+ * next to a state change can silently diverge from it afterwards. A
636
+ * wiring error: deterministic bug at the call site, the remedy is
637
+ * minting through the constructors. The gate catches accidents, not
638
+ * adversaries: code in the same process can fake the brand.
639
+ */
640
+ var UnmintedEventError = class extends KitWiringError {
641
+ constructor(eventType) {
642
+ super("UNMINTED_EVENT", `Event "${eventType}" was not minted by a domain-event constructor or aggregate createEvent(...) helper. Those constructors deep-freeze the event and defensively copy payload and metadata; a mutable event could diverge from the state change it records.`);
643
+ }
644
+ };
645
+ /**
646
+ * Thrown by `recordPendingEvents` when the aggregate's pending-event list
647
+ * changes while its events are being stamped: a stamp provider that
648
+ * directly or transitively triggers a new decision on the same aggregate
649
+ * would otherwise have that decision silently discarded when recording
650
+ * replaces the pending list. Recording is atomic: when this guard fires,
651
+ * every decision (including the re-entrant one) remains unrecorded. A
652
+ * wiring error: deterministic bug at the call site, the remedy is keeping
653
+ * stamp providers free of domain decisions.
654
+ */
655
+ var ReentrantEventRecordingError = class extends KitWiringError {
656
+ identity;
657
+ constructor(options) {
658
+ super("REENTRANT_EVENT_RECORDING", `Pending events of aggregate ${describeAggregateIdentity(options.identity)} changed while recordPendingEvents was stamping them. A stamp provider must not trigger new decisions on the aggregate being recorded; make every domain decision first, then record.`);
659
+ this.identity = detachAggregateIdentity(options.identity);
660
+ }
661
+ };
662
+ /**
663
+ * Thrown when two facts of one aggregate would carry the same `eventId`.
664
+ * Two causes, two sites: the aggregate rejects a recorded event that is
665
+ * already pending at the append, before the state moves; and
666
+ * `recordPendingEvents` rejects a stamp provider that returns one reused
667
+ * stamp (or repeats an explicit id). Either would mint two distinct facts
668
+ * sharing one identity, and downstream idempotent consumers keyed on
669
+ * `eventId` silently drop one of them. A wiring error: deterministic bug at
670
+ * the append site or in the stamp provider, the remedy is one fresh
671
+ * identity per fact.
672
+ */
673
+ var DuplicateEventIdError = class extends KitWiringError {
674
+ identity;
675
+ /** The event id two pending events would have shared. */
676
+ eventId;
677
+ constructor(options) {
678
+ super("DUPLICATE_EVENT_ID", `Two pending events of aggregate ${describeAggregateIdentity(options.identity)} carry the same eventId "${options.eventId}". Each fact needs its own event id: append a recorded event once, and return a fresh stamp per decision from the stamp provider.`);
679
+ this.identity = detachAggregateIdentity(options.identity);
680
+ this.eventId = options.eventId;
681
+ }
682
+ };
683
+ /**
684
+ * Thrown when a recording would grow the pending list of an aggregate past
685
+ * `AggregateConfig.maxPendingEvents`. The check runs before the state
686
+ * moves, so the rejected decision records nothing and moves nothing. The
687
+ * limit is a modelling signal, not a runtime budget: a decision that emits
688
+ * hundreds of facts points at a missing aggregate boundary, and a retry
689
+ * repeats it. A wiring error: split the aggregate, or emit fewer facts
690
+ * per decision.
691
+ */
692
+ var PendingEventLimitExceededError = class extends KitWiringError {
693
+ identity;
694
+ limit;
695
+ pending;
696
+ added;
697
+ constructor(options) {
698
+ super("PENDING_EVENT_LIMIT_EXCEEDED", `Aggregate ${describeAggregateIdentity(options.identity)} holds ${options.pending} pending event(s) and cannot record ${options.added} more: maxPendingEvents is ${options.limit}. A decision that emits this many facts points at a missing aggregate boundary.`);
699
+ this.identity = detachAggregateIdentity(options.identity);
700
+ this.limit = options.limit;
701
+ this.pending = options.pending;
702
+ this.added = options.added;
703
+ }
704
+ };
705
+ /**
706
+ * Thrown by the post-commit acknowledgement of an aggregate when the
707
+ * committed batch is not the prefix of its pending events any more. The
708
+ * batch is longer than the pending list, or an event in it is not the
709
+ * pending event at the same position. Acknowledging such a batch would
710
+ * drop decisions the commit never persisted or keep events it did. The
711
+ * pending list stays untouched. A wiring error in application commit
712
+ * orchestration: acknowledge exactly the batch that was enrolled, once.
713
+ */
714
+ var PendingEventBatchMismatchError = class extends KitWiringError {
715
+ identity;
716
+ batchLength;
717
+ pendingLength;
718
+ constructor(options) {
719
+ super("PENDING_EVENT_BATCH_MISMATCH", `The committed batch of ${options.batchLength} event(s) is no longer the pending prefix of aggregate ${describeAggregateIdentity(options.identity)} (${options.pendingLength} pending). Acknowledge exactly the batch that was enrolled, once.`);
720
+ this.identity = detachAggregateIdentity(options.identity);
721
+ this.batchLength = options.batchLength;
722
+ this.pendingLength = options.pendingLength;
723
+ }
724
+ };
725
+ /**
726
+ * Thrown by persisted-event consumers (including `replayHistory` and
727
+ * `Projector`) when an event carries an
728
+ * `aggregateId` or `aggregateType` that names a different aggregate:
729
+ * the persisted row belongs to someone else (a miswired stream read,
730
+ * ids colliding across aggregate types, a corrupted store). An
731
+ * `InfrastructureError`, NOT a `DomainError` (same posture as
732
+ * {@link SnapshotSchemaMismatchError}): a wrong aggregate identity is data
733
+ * corruption or wiring, never an expected business rejection, so it
734
+ * must not be absorbed by generic domain error handling or presented
735
+ * as a 4xx. It therefore PROPAGATES as a throw through the replay
736
+ * methods' `Result` contract (which reserves `Err` for `DomainError`),
737
+ * after the usual all-or-nothing rollback. History events without the
738
+ * optional aggregate identity fields pass unchecked (the fields are optional on
739
+ * the event shape); new events are covered by
740
+ * {@link MisattributedEventError}.
741
+ */
742
+ var ForeignEventError = class extends InfrastructureError {
743
+ /** Identity of the aggregate that received the event. */
744
+ expected;
745
+ /** Aggregate identity fields the event carries. */
746
+ actual;
747
+ eventType;
748
+ constructor(options) {
749
+ super({
750
+ code: "FOREIGN_EVENT",
751
+ message: `Persisted event "${options.eventType}" belongs to ${describeEventIdentity(options)}, not to ${describeAggregateIdentity(options.expected)}: the stream row belongs to a different aggregate.`
752
+ });
753
+ this.expected = detachAggregateIdentity(options.expected);
754
+ this.actual = detachPartialAggregateIdentity(options.actual);
755
+ this.eventType = options.eventType;
756
+ }
757
+ };
758
+ function describeReportedHead(head) {
759
+ if (typeof head === "number" && Number.isFinite(head)) return String(head);
760
+ return `${showReportedHead(head)} of type ${typeof head}`;
761
+ }
762
+ function showReportedHead(head) {
763
+ if (typeof head === "string") return JSON.stringify(head);
764
+ if (typeof head === "object" && head !== null) return Object.prototype.toString.call(head);
765
+ return String(head);
766
+ }
767
+ function eventStreamPageReasonMessage(stream, options) {
768
+ const target = `target version ${String(options.targetVersion)}`;
769
+ switch (options.reason) {
770
+ case "empty_page": return `The read of ${stream} returned an empty page after version ${options.fromVersion} toward ${target}. A page holds at least one event while events remain.`;
771
+ case "stream_vanished": return `readStream reported ${stream} absent after version ${options.fromVersion} while the replay had not reached ${target}. A stream is append-only, and only a physical removal deletes it; check for a removal that ran during the read.`;
772
+ case "page_past_target": return `The read of ${stream} returned ${String(options.eventCount)} events after version ${options.fromVersion}, more than the window (${options.fromVersion}, ${String(options.targetVersion)}] holds.`;
773
+ case "page_over_limit": return `The read of ${stream} returned ${String(options.eventCount)} events after version ${options.fromVersion}, more than the limit of ${String(options.limit)} it asked for.`;
774
+ case "head_regressed": return `readStream reported ${stream} at head ${String(options.lastVersion)} after version ${options.fromVersion}, below the head ${String(options.firstPageLastVersion)} of the first page. A stream is append-only, so its head never moves back.`;
775
+ case "invalid_head": return `readStream reported ${stream} with head ${describeReportedHead(options.lastVersion)} after version ${options.fromVersion}. The head of an existing stream is a safe integer of at least 1; report a stream without events as absent.`;
776
+ }
777
+ }
778
+ /**
779
+ * Thrown when a page of a stream read breaks the `readStream` contract.
780
+ * `readStreamPages` checks every page it reads, and
781
+ * `reconstituteAggregateFromStreamPages` checks every page an adapter that
782
+ * pages on its own hands over. Both throw this error for the same defect.
783
+ *
784
+ * The `reason` names the defect. `empty_page`: the page holds no event,
785
+ * but events remain before the target version. `stream_vanished`: a
786
+ * continuation page reports the stream absent. `page_past_target`: the
787
+ * page holds more events than its window has left. `page_over_limit`: the
788
+ * page holds more events than the `limit` of the read. `head_regressed`: a
789
+ * continuation page reports a head below the head of the first page.
790
+ * `invalid_head`: a page of an existing stream reports a head that is not
791
+ * a safe integer of at least 1, for example `0` for a stream without
792
+ * events, or a string from a driver that returns big integers as text.
793
+ *
794
+ * No case is retryable. Fix the adapter: run `createEventStoreContractTests`
795
+ * against an EventStore adapter, and `createReplayableStreamPagesContractTests`
796
+ * against an adapter that pages on its own. Two causes lie outside the
797
+ * adapter. A stream is append-only, and only a physical removal deletes it
798
+ * (the repository guide, "Domain deletion versus physical removal"), so a
799
+ * removal that runs during the read gives `stream_vanished`. An upcaster
800
+ * that splits one stored event into several gives `page_past_target` at
801
+ * the replay, because the replay counts one version per event.
802
+ */
803
+ var InvalidEventStreamPageError = class extends InfrastructureError {
804
+ identity;
805
+ reason;
806
+ fromVersion;
807
+ targetVersion;
808
+ lastVersion;
809
+ firstPageLastVersion;
810
+ eventCount;
811
+ limit;
812
+ constructor(options) {
813
+ const stream = `${describeAggregateIdentity(options.identity)}`;
814
+ super({
815
+ code: "INVALID_EVENT_STREAM_PAGE",
816
+ message: eventStreamPageReasonMessage(stream, options)
817
+ });
818
+ this.identity = detachAggregateIdentity(options.identity);
819
+ this.reason = options.reason;
820
+ this.fromVersion = options.fromVersion;
821
+ this.targetVersion = options.targetVersion;
822
+ this.lastVersion = options.lastVersion;
823
+ this.firstPageLastVersion = options.firstPageLastVersion;
824
+ this.eventCount = options.eventCount;
825
+ this.limit = options.limit;
826
+ }
827
+ };
828
+ function replayTargetMismatchMessage(stream, options) {
829
+ switch (options.reason) {
830
+ case "target_not_at_cursor": return `Replay target for ${stream} stands at version ${options.actualVersion}, but the read continues after version ${options.fromVersion}. The reconstitution factory reports a version other than the read cursor.`;
831
+ case "pages_short_of_target": return `Replay of ${stream} ended at version ${options.actualVersion}, short of the pinned target version ${options.targetVersion}. The read stopped before the target version, the stream has a gap after version ${options.actualVersion}, or an upcaster merged stored events.`;
832
+ }
833
+ }
834
+ /**
835
+ * Thrown by `reconstituteAggregateFromStreamPages`, or by a load recipe
836
+ * that replays on its own, when the replay target does not line up with the
837
+ * read. Events carry no stream position, so the aggregate cannot detect a
838
+ * tail that overlaps or misses the version it was reconstituted at. Only
839
+ * the caller, which pinned the target version, can compare. A single page
840
+ * that breaks its window is an {@link InvalidEventStreamPageError} instead.
841
+ *
842
+ * The `reason` names the check that failed. `target_not_at_cursor`: the
843
+ * replay target stands at a version other than the `fromVersion` the read
844
+ * used, found before any page is replayed; a reconstitution factory reports
845
+ * the wrong version. `pages_short_of_target`: the pages ended before the
846
+ * target version. The read stopped early, the stream has a gap, or an
847
+ * upcaster merged several stored events into one. For an EventStore
848
+ * adapter, run `createEventStoreContractTests` and
849
+ * `createEsRepositoryContractTests` against it and fix its windowing. None
850
+ * of the cases is retryable. A snapshot beyond its stream does not reach
851
+ * the replay: `readStreamPages` reports that window as unreachable first,
852
+ * and an adapter that pages on its own must do the same.
853
+ */
854
+ var ReplayTargetMismatchError = class extends InfrastructureError {
855
+ identity;
856
+ reason;
857
+ fromVersion;
858
+ targetVersion;
859
+ actualVersion;
860
+ constructor(options) {
861
+ const message = replayTargetMismatchMessage(`${describeAggregateIdentity(options.identity)}`, options);
862
+ super({
863
+ code: "REPLAY_TARGET_MISMATCH",
864
+ message
865
+ });
866
+ this.identity = detachAggregateIdentity(options.identity);
867
+ this.reason = options.reason;
868
+ this.fromVersion = options.fromVersion;
869
+ this.targetVersion = options.targetVersion;
870
+ this.actualVersion = options.actualVersion;
871
+ }
872
+ };
873
+ /**
874
+ * The `Err` of `reconstituteAggregateFromStreamPages`: the aggregate
875
+ * rejected a stored event while it replayed a page of the stream.
876
+ *
877
+ * The window `(fromVersion, toVersion]` locates the rejected page, and
878
+ * `cause` holds the `DomainError` of the aggregate. A stored stream that
879
+ * the domain cannot replay is a defect of the stored data, not a request
880
+ * the caller can correct. So this is an `InfrastructureError`, and it is
881
+ * not retryable. An empty window means that the replay target rejected an
882
+ * empty history before the first page.
883
+ */
884
+ var ReplayRejectedError = class extends InfrastructureError {
885
+ identity;
886
+ fromVersion;
887
+ toVersion;
888
+ constructor(options) {
889
+ const stream = `${describeAggregateIdentity(options.identity)}`;
890
+ const rejected = `${options.cause.code}: ${options.cause.message}`;
891
+ super({
892
+ code: "REPLAY_REJECTED",
893
+ message: options.fromVersion === options.toVersion ? `The replay target of ${stream} rejected an empty history at version ${options.fromVersion} with ${rejected}` : `The aggregate ${stream} rejected a stored event in the page (${options.fromVersion}, ${options.toVersion}] with ${rejected}`,
894
+ cause: options.cause
895
+ });
896
+ this.identity = detachAggregateIdentity(options.identity);
897
+ this.fromVersion = options.fromVersion;
898
+ this.toVersion = options.toVersion;
899
+ }
900
+ };
901
+ /**
902
+ * Thrown when an event harvested from an aggregate cannot be safely composed
903
+ * into a commit envelope, or when an outbox can prove that accepting a
904
+ * candidate would violate its event identity/source chain. Harvest failures
905
+ * include missing `aggregateId` / `aggregateType` (downstream routing would
906
+ * break), or an
907
+ * eventful persisted aggregate did not advance its version (two commits
908
+ * would receive the same source position). These programming bugs are
909
+ * deterministic and fail identically on every retry.
910
+ *
911
+ * Deliberately **not** an {@link InfrastructureError} (same reasoning as
912
+ * {@link MissingHandlerError}): this is a deterministic programming error,
913
+ * not a transient storage failure. A `catch (e instanceof InfrastructureError)`
914
+ * retry handler, or a retrying `TransactionScope`, must NOT mask it or loop on
915
+ * it forever; it should crash loud so the caller misuse surfaces in
916
+ * development. This is why `withCommit` throws it directly and
917
+ * `UnitOfWork.run` passes it through unchanged instead of wrapping it in
918
+ * `CommitError`.
919
+ */
920
+ var EventHarvestError = class extends KitWiringError {
921
+ eventType;
922
+ constructor(message, eventType) {
923
+ super("EVENT_HARVEST_FAILED", message);
924
+ this.eventType = eventType;
925
+ }
926
+ };
927
+ /**
928
+ * Thrown at bootstrap when the global key of a kit capability registry
929
+ * already holds a value that is not a registry: another module claimed
930
+ * the key. The kit neither shares that value nor overwrites it, because a
931
+ * silent replacement would break whichever module owned the key first. A
932
+ * wiring error in the host process; the remedy is one owner per key.
933
+ */
934
+ var CapabilityRegistryConflictError = class extends KitWiringError {
935
+ key;
936
+ constructor(key) {
937
+ super("CAPABILITY_REGISTRY_CONFLICT", `The global key ${String(key)} holds a value that is not a capability registry of this package. Another module claimed the key; the kit refuses to share or overwrite it.`);
938
+ this.key = key;
939
+ }
940
+ };
941
+ /**
942
+ * Thrown when a kit operation receives an instance that this package did
943
+ * not construct: a structural lookalike, a repository DTO, or an instance
944
+ * from an incompatible copy of the package. Such an instance carries none
945
+ * of the kit-managed capabilities the operation needs. A wiring error:
946
+ * extend the kit's base classes and run one compatible package copy.
947
+ */
948
+ var UnmanagedInstanceError = class extends KitWiringError {
949
+ operation;
950
+ subject;
951
+ instanceId;
952
+ constructor(operation, subject, instanceId, detail) {
953
+ super("UNMANAGED_INSTANCE", `${operation} requires an instance constructed by this package; ${instanceId === void 0 ? subject : `${subject} ${String(instanceId)}`} carries no kit-managed capability. Construct it through this package and run one compatible package copy; a structural lookalike or an instance from another copy cannot be managed.` + (detail === void 0 ? "" : ` ${detail}`));
954
+ this.operation = operation;
955
+ this.subject = subject;
956
+ this.instanceId = instanceId;
957
+ }
958
+ };
959
+ /**
960
+ * Shared guard for the loud-rejection contract on own `__proto__` data
961
+ * keys (the shape `JSON.parse` produces for hostile rows, bodies, or
962
+ * envelopes): used by `Entity` state copies and the event metadata
963
+ * helpers. One implementation so the contract cannot drift.
964
+ * Module-internal export; not part of the package entries.
965
+ */
966
+ function assertNoHostileOwnProtoKey(value, subject) {
967
+ if (Object.hasOwn(value, "__proto__")) throw new HostileStateKeyError("__proto__", subject);
968
+ }
969
+ /**
970
+ * Produced by the in-memory `CommandBus` / `QueryBus` when a message is
971
+ * dispatched for a type no handler was registered under: a wiring bug
972
+ * (typo in the type string, missing `register` call at bootstrap), not
973
+ * a domain or infrastructure failure.
974
+ *
975
+ * Carries the `WIRING` category (same crash-loud family as
976
+ * {@link MissingHandlerError}), and since v3 it is THROWN by `execute`
977
+ * and `executeUnsafe` alike, never delivered through the error channel:
978
+ * the channel carries expected failures a registered handler produced,
979
+ * and a generic err-branch must not absorb a mis-wired bus. Catch it
980
+ * only at a boundary that turns bugs into 500s.
981
+ */
982
+ var UnregisteredHandlerError = class extends KitWiringError {
983
+ busKind;
984
+ messageType;
985
+ constructor(options) {
986
+ super("UNREGISTERED_HANDLER", `No handler registered for ${options.busKind} type: ${options.messageType}`);
987
+ this.busKind = options.busKind;
988
+ this.messageType = options.messageType;
989
+ }
990
+ };
991
+ /**
992
+ * Produced by `CommandBus.register` / `QueryBus.register` when a handler
993
+ * is registered for a type that already has one: silent replacement would
994
+ * turn the first handler into dead code with no signal, so the wiring bug
995
+ * surfaces at registration time. Same crash-loud family as
996
+ * {@link UnregisteredHandlerError}; catch it only at a boundary that
997
+ * turns bugs into 500s.
998
+ */
999
+ var DuplicateHandlerRegistrationError = class extends KitWiringError {
1000
+ busKind;
1001
+ messageType;
1002
+ constructor(options) {
1003
+ super("DUPLICATE_HANDLER_REGISTRATION", `A handler for ${options.busKind} type "${options.messageType}" is already registered; the duplicate would silently shadow the first. Register each type exactly once at bootstrap.`);
1004
+ this.busKind = options.busKind;
1005
+ this.messageType = options.messageType;
1006
+ }
1007
+ };
1008
+ /**
1009
+ * Produced by the in-memory `CommandBus` / `QueryBus` when the configured
1010
+ * `mapExpectedError` policy fails while classifying a registered handler's
1011
+ * failure, either by throwing or by returning an invalid decision. A broken
1012
+ * mapper is a wiring bug: letting its failure propagate bare would
1013
+ * replace the handler's original failure entirely, and the rest of the
1014
+ * kit is fastidious about never letting a secondary failure mask the
1015
+ * primary one (`RollbackError.rollbackCause`, the neutralized observers).
1016
+ *
1017
+ * The handler's original failure is preserved as `cause` (so cause-chain
1018
+ * walks, retryability checks, and error-type mapping keep working) and
1019
+ * the mapper's own failure rides along as {@link mapperCause}.
1020
+ *
1021
+ * Carries the `WIRING` category (same crash-loud family as
1022
+ * {@link MissingHandlerError} and {@link UnregisteredHandlerError}): it is
1023
+ * thrown, never delivered through the error channel.
1024
+ */
1025
+ var ErrorMapperFailedError = class extends KitWiringError {
1026
+ busKind;
1027
+ /** The mapper failure or invalid-decision diagnostic. */
1028
+ mapperCause;
1029
+ constructor(options) {
1030
+ super("ERROR_MAPPER_FAILED", `The ${options.busKind} bus mapExpectedError policy failed while classifying a handler failure. The original handler error is preserved as cause; the mapper's own failure as mapperCause.`, options.handlerError);
1031
+ this.busKind = options.busKind;
1032
+ this.mapperCause = options.mapperError;
1033
+ }
1034
+ };
1035
+ /**
1036
+ * Thrown at the end of a `UnitOfWork.run` when an aggregate that was
1037
+ * loaded into the identity map changed but no `update` intent was registered.
1038
+ * Without this guard the changed state or pending events would be silently
1039
+ * dropped.
1040
+ *
1041
+ * Deliberately **not** an `InfrastructureError` (same posture as
1042
+ * {@link MissingHandlerError}): a programming bug that must crash loud,
1043
+ * not be absorbed by a generic infrastructure-error handler. The throw
1044
+ * happens inside the transaction, so the unit of work rolls back and
1045
+ * leaves no partial state.
1046
+ *
1047
+ * **Scope of the guard.** A best-effort runtime safety net, not a proof.
1048
+ * It sees aggregates that repository adapters register through
1049
+ * `tracking.trackLoaded` and detects ordinary state changes through the version
1050
+ * captured at load. The pending-event count remains a second guard for an
1051
+ * invalid event-only mutation that did not advance the version. A freshly
1052
+ * created aggregate that is never passed to `add` is invisible to the kit.
1053
+ *
1054
+ * An append-only repository installs no `update`, so for its aggregate the
1055
+ * message names the rule instead: a loaded append-only aggregate must not
1056
+ * change.
1057
+ */
1058
+ var UnenrolledChangesError = class extends KitWiringError {
1059
+ identity;
1060
+ constructor(options) {
1061
+ super("UNENROLLED_CHANGES", `Aggregate ${describeAggregateIdentity(options.identity)} was loaded and changed in this unit of work, ` + (options.appendOnly ? "but its repository is append-only and installs no update. An append-only aggregate must not change after add." : "but no update intent was registered. Call repository.update(aggregate) after the final domain decision so state and events flush together."));
1062
+ this.identity = detachAggregateIdentity(options.identity);
1063
+ }
1064
+ };
1065
+ /**
1066
+ * Thrown when an aggregate removed within the current unit of work is added,
1067
+ * updated, or tracked again in the same operation. Removal is final within an
1068
+ * operation; writing afterwards would resurrect the row, which is always a
1069
+ * use-case bug.
1070
+ *
1071
+ * Carries the `WIRING` category (same reasoning as
1072
+ * {@link MissingHandlerError}): a programming bug that should crash
1073
+ * loud, not be absorbed by a generic infrastructure-error handler.
1074
+ */
1075
+ var AggregateDeletedError = class extends KitWiringError {
1076
+ identity;
1077
+ constructor(options) {
1078
+ super("AGGREGATE_DELETED", `Aggregate ${describeAggregateIdentity(options.identity)} was removed in this unit of work and cannot be added, updated, tracked, or removed through another instance again. Removal is final within an operation. A repeated remove of the SAME instance is an accepted no-op; if the aggregate must remain, do not remove it.`);
1079
+ this.identity = detachAggregateIdentity(options.identity);
1080
+ }
1081
+ };
1082
+ var AggregateNotFoundError = class extends InfrastructureError {
1083
+ identity;
1084
+ constructor(options) {
1085
+ super({
1086
+ code: "AGGREGATE_NOT_FOUND",
1087
+ message: `Aggregate not found: ${describeAggregateIdentity(options.identity)}`,
1088
+ cause: options.cause
1089
+ });
1090
+ this.identity = detachAggregateIdentity(options.identity);
1091
+ }
1092
+ };
1093
+ var DuplicateAggregateError = class extends InfrastructureError {
1094
+ identity;
1095
+ constructor(options) {
1096
+ super({
1097
+ code: "DUPLICATE_AGGREGATE",
1098
+ message: `Duplicate aggregate: ${describeAggregateIdentity(options.identity)} already exists`,
1099
+ cause: options.cause
1100
+ });
1101
+ this.identity = detachAggregateIdentity(options.identity);
1102
+ }
1103
+ };
1104
+ var SnapshotSchemaMismatchError = class extends InfrastructureError {
1105
+ identity;
1106
+ expectedSchemaVersion;
1107
+ actualSchemaVersion;
1108
+ constructor(options) {
1109
+ super({
1110
+ code: "SNAPSHOT_SCHEMA_MISMATCH",
1111
+ message: `Snapshot schema mismatch on ${describeAggregateIdentity(options.identity)}: the snapshot model expects schema ${options.expectedSchemaVersion}, the stored snapshot carries ${options.actualSchemaVersion}. Override the model's migrate function to upgrade old snapshots, or discard the snapshot and refold from the full event stream.`
1112
+ });
1113
+ this.identity = detachAggregateIdentity(options.identity);
1114
+ this.expectedSchemaVersion = options.expectedSchemaVersion;
1115
+ this.actualSchemaVersion = options.actualSchemaVersion;
1116
+ }
1117
+ };
1118
+ /**
1119
+ * Surfaced by a Unit-of-Work flush when the aggregate's expected version does
1120
+ * not match the version currently persisted: i.e. another writer
1121
+ * updated the aggregate concurrently. The canonical optimistic-
1122
+ * concurrency signal; the App-Service typically reloads, re-applies
1123
+ * the use case, and retries, or surfaces HTTP 409 to the caller.
1124
+ *
1125
+ * **Retry means a FRESH unit of work** (a new `UnitOfWork.run()` /
1126
+ * `withCommit` invocation): reload, re-apply, and register `update` again. Do NOT catch this
1127
+ * inside the same `run()` callback and continue: the failed aggregate
1128
+ * is already enrolled (its events would be committed for a write that
1129
+ * never happened) and the identity map still serves the same stale
1130
+ * instance to any in-place "reload".
1131
+ *
1132
+ * `InfrastructureError` because the persistence layer (not a domain
1133
+ * rule) detects the race. Its `retryable` follows the reason, so the
1134
+ * `isRetryable` predicate from `@shirudo/base-error` picks up every reason
1135
+ * but `version_unchanged`, which names a defect of the adapter.
1136
+ */
1137
+ var ConcurrencyConflictError = class extends InfrastructureError {
1138
+ identity;
1139
+ /** The write that failed, or `null` when the raising code does not know it. */
1140
+ intent;
1141
+ expectedVersion;
1142
+ /** The stored version, or `null` when none exists to name. */
1143
+ actualVersion;
1144
+ reason;
1145
+ constructor(options) {
1146
+ super({
1147
+ code: "CONCURRENCY_CONFLICT",
1148
+ message: concurrencyConflictMessage(options),
1149
+ cause: options.cause,
1150
+ retryable: options.reason !== "version_unchanged"
1151
+ });
1152
+ this.identity = detachAggregateIdentity(options.identity);
1153
+ this.intent = options.intent ?? null;
1154
+ this.expectedVersion = options.expectedVersion;
1155
+ this.actualVersion = options.actualVersion ?? null;
1156
+ this.reason = options.reason;
1157
+ }
1158
+ };
1159
+ /**
1160
+ * Sets the write intent on a conflict that its raising code could not name,
1161
+ * and renders the message again. The conflict keeps its class, its fields,
1162
+ * its cause, and its throw site.
1163
+ */
1164
+ function attributeConflictIntent(conflict, intent) {
1165
+ if (conflict.intent === intent) return;
1166
+ Reflect.set(conflict, "intent", intent);
1167
+ rewriteErrorMessage(conflict, concurrencyConflictMessage({
1168
+ identity: conflict.identity,
1169
+ intent,
1170
+ expectedVersion: conflict.expectedVersion,
1171
+ reason: conflict.reason,
1172
+ actualVersion: conflict.actualVersion
1173
+ }));
1174
+ }
1175
+ /**
1176
+ * Replaces the message of an error, and the message in the first line of its
1177
+ * stack where the engine lets the kit write it.
1178
+ */
1179
+ function rewriteErrorMessage(error, message) {
1180
+ const header = `${error.name}: ${error.message}`;
1181
+ const stack = Reflect.get(error, "stack");
1182
+ Reflect.set(error, "message", message);
1183
+ if (typeof stack === "string" && stack.startsWith(header)) Reflect.set(error, "stack", `${error.name}: ${message}${stack.slice(header.length)}`);
1184
+ }
1185
+ function concurrencyConflictMessage(options) {
1186
+ const aggregate = describeAggregateIdentity(options.identity);
1187
+ const site = options.intent == null ? aggregate : `${options.intent} of ${aggregate}`;
1188
+ switch (options.reason) {
1189
+ case "stale_version": return `Concurrency conflict on ${site}: expected version ${options.expectedVersion}, stored version ${options.actualVersion}.`;
1190
+ case "version_unchanged": return `Concurrency conflict on ${site}: the write matched nothing, although the stored version is ${options.actualVersion}. Its statement carries a condition beyond the version, or its version read answered from a transaction snapshot. Drop the extra condition, or read the version with a locking read, for example SELECT ... FOR SHARE.`;
1191
+ case "aggregate_absent": return `Concurrency conflict on ${site}: expected version ${options.expectedVersion}, but the aggregate no longer exists.`;
1192
+ case "version_unknown": return `Concurrency conflict on ${site}: expected version ${options.expectedVersion}. The stored version could not be read; the read failure is the cause.`;
1193
+ }
1194
+ }
1195
+ /**
1196
+ * Thrown by `IdempotencyStore.claim()` when the same idempotency key
1197
+ * arrives with a DIFFERENT command fingerprint than the one it was
1198
+ * first claimed with: the caller is reusing a key for a different
1199
+ * command. Replaying the stored outcome would answer a question that
1200
+ * was never asked; rejecting is the only safe reaction.
1201
+ *
1202
+ * `InfrastructureError` because the store detects the collision, same
1203
+ * delegation model as {@link DuplicateAggregateError}. NOT retryable:
1204
+ * re-sending the same mismatched pair cannot succeed. Map it to an
1205
+ * unprocessable/conflict application outcome.
1206
+ */
1207
+ var IdempotencyKeyReuseError = class extends InfrastructureError {
1208
+ key;
1209
+ storedFingerprint;
1210
+ receivedFingerprint;
1211
+ constructor(options) {
1212
+ super({
1213
+ code: "IDEMPOTENCY_KEY_REUSE",
1214
+ message: `Idempotency key reuse on "${options.key}": stored fingerprint ${options.storedFingerprint}, received ${options.receivedFingerprint}`,
1215
+ cause: options.cause
1216
+ });
1217
+ this.key = options.key;
1218
+ this.storedFingerprint = options.storedFingerprint;
1219
+ this.receivedFingerprint = options.receivedFingerprint;
1220
+ }
1221
+ };
1222
+ /**
1223
+ * Thrown when a leased idempotency owner tries to renew, complete, or
1224
+ * reconcile through a claim token that no longer owns the key. The usual
1225
+ * cause is lease expiry followed by a successful takeover. The stale
1226
+ * execution must abort before its transaction commits; retrying starts from
1227
+ * a fresh claim or replays the winner.
1228
+ */
1229
+ var IdempotencyClaimLostError = class extends InfrastructureError {
1230
+ key;
1231
+ token;
1232
+ constructor(options) {
1233
+ super({
1234
+ code: "IDEMPOTENCY_CLAIM_LOST",
1235
+ message: `Idempotency claim for key "${options.key}" no longer belongs to token "${options.token}"`,
1236
+ cause: options.cause,
1237
+ retryable: true
1238
+ });
1239
+ this.key = options.key;
1240
+ this.token = options.token;
1241
+ }
1242
+ };
1243
+ /**
1244
+ * Thrown by `IdempotencyStore.claim()` when the key is already claimed
1245
+ * by an execution that has not completed yet: the first delivery of the
1246
+ * command is still running (or crashed mid-flight on a
1247
+ * non-transactional store). Retryable by design: a later retry either
1248
+ * finds the completed outcome and replays it, or finds the claim
1249
+ * released (rolled back) and executes fresh. `RetryingTransactionScope`
1250
+ * picks this up through the `retryable` flag without extra wiring.
1251
+ */
1252
+ var IdempotencyInFlightError = class extends InfrastructureError {
1253
+ key;
1254
+ constructor(options) {
1255
+ super({
1256
+ code: "IDEMPOTENCY_IN_FLIGHT",
1257
+ message: `Idempotency key "${options.key}" is claimed by an execution that has not completed yet`,
1258
+ cause: options.cause,
1259
+ retryable: true
1260
+ });
1261
+ this.key = options.key;
1262
+ }
1263
+ };
1264
+ /**
1265
+ * An expired staged outcome cannot be replayed or discarded until the
1266
+ * application checks the authoritative write model. Immediate retry without
1267
+ * that evidence cannot make progress, so this error is deliberately not
1268
+ * marked retryable.
1269
+ */
1270
+ var IdempotencyReconciliationRequiredError = class extends InfrastructureError {
1271
+ key;
1272
+ fingerprint;
1273
+ token;
1274
+ expiredAt;
1275
+ constructor(options) {
1276
+ super({
1277
+ code: "IDEMPOTENCY_RECONCILIATION_REQUIRED",
1278
+ message: `Idempotency key "${options.key}" has an expired staged outcome; consult the authoritative write model before confirming or releasing it`
1279
+ });
1280
+ this.key = options.key;
1281
+ this.fingerprint = options.fingerprint;
1282
+ this.token = options.token;
1283
+ this.expiredAt = options.expiredAt;
1284
+ }
1285
+ };
1286
+ /**
1287
+ * Thrown by `IdempotencyStore.complete()` when no pending claim exists
1288
+ * for the key: `complete` ran without a preceding successful `claim`
1289
+ * in the same execution, or against a key whose claim was already
1290
+ * completed or abandoned. Always a wiring bug in hand-rolled
1291
+ * orchestration (`withIdempotentCommit` cannot produce it), hence the
1292
+ * crash-loud category.
1293
+ */
1294
+ var IdempotencyCompletionWithoutClaimError = class extends KitWiringError {
1295
+ key;
1296
+ constructor(key) {
1297
+ super("IDEMPOTENCY_COMPLETED_WITHOUT_CLAIM", `IdempotencyStore.complete() called for key "${key}" without a pending claim; call claim() first (or use withIdempotentCommit)`);
1298
+ this.key = key;
1299
+ }
1300
+ };
1301
+
1302
+ //#endregion
1303
+ export { isDomainErrorLike as $, MissingHandlerError as A, SnapshotCorruptedError as B, InvalidEventStreamPageError as C, MisattributedEventError as D, KitWiringError as E, ProjectionOrderViolationError as F, UnmintedEventError as G, SnapshotVersionNotRestoredError as H, ProjectionReceiptViolationError as I, UnreplayableAggregateError as J, UnprojectableEventError as K, ReentrantEventRecordingError as L, PendingEventLimitExceededError as M, ProjectionGapError as N, MissingEntityIdError as O, ProjectionIdentityViolationError as P, detachAggregateIdentity as Q, ReplayRejectedError as R, InvalidCommandMessageError as S, InvalidVersionError as T, UnenrolledChangesError as U, SnapshotSchemaMismatchError as V, UnmanagedInstanceError as W, attributeConflictIntent as X, assertNoHostileOwnProtoKey as Y, describeAggregateIdentity as Z, IdempotencyInFlightError as _, DirectStateMutationError as a, InMemoryCapacityExceededError as b, DuplicateEventIdError as c, EventHarvestError as d, isInfrastructureErrorLike as et, FoldReturnedNoStateError as f, IdempotencyCompletionWithoutClaimError as g, IdempotencyClaimLostError as h, ConcurrencyConflictError as i, PendingEventBatchMismatchError as j, MissingFoldError as k, DuplicateHandlerRegistrationError as l, HostileStateKeyError as m, AggregateNotFoundError as n, rewriteErrorMessage as nt, DomainError as o, ForeignEventError as p, UnregisteredHandlerError as q, CapabilityRegistryConflictError as r, DuplicateAggregateError as s, AggregateDeletedError as t, isWiringErrorLike as tt, ErrorMapperFailedError as u, IdempotencyKeyReuseError as v, InvalidIntegrationMessageError as w, InfrastructureError as x, IdempotencyReconciliationRequiredError as y, ReplayTargetMismatchError as z };
1304
+ //# sourceMappingURL=kit-errors.js.map