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

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,785 @@
1
+ import { StructuredError } from "@shirudo/base-error";
2
+
3
+ //#region src/core/errors.d.ts
4
+ /**
5
+ * **The kit's error identity model (since v3).** Every kit error is a
6
+ * structured error carrying exactly ONE identifier: `code`, a stable
7
+ * SCREAMING_SNAKE string, and `error.name === error.code` by design, so
8
+ * there is no name/code drift and nothing to keep in sync. `category`
9
+ * follows the class hierarchy mechanically (`"DOMAIN"`,
10
+ * `"INFRASTRUCTURE"`, or `"WIRING"` for the crash-loud family) and
11
+ * `retryable` is a plain boolean field.
12
+ *
13
+ * **No base-error adoption required.** Consumers branch with a plain
14
+ * `switch (error.code)`, catch via `instanceof DomainError` /
15
+ * `instanceof InfrastructureError` (exported from this kit), and read
16
+ * `retryable` / `cause` as ordinary properties. base-error's toolbox
17
+ * (`matchError` exhaustive dispatch, `isStructuredError`, the
18
+ * public-error catalog and `toProblem`) works on every kit error as an
19
+ * OPT-IN benefit on top, never as a prerequisite.
20
+ */
21
+ /**
22
+ * Options for consumer subclasses of {@link DomainError} and
23
+ * {@link InfrastructureError}: the `code` (which also becomes
24
+ * `error.name`) and the technical `message` are the only obligations;
25
+ * `retryable` defaults to `false` and the category is fixed by the base.
26
+ */
27
+ interface KitErrorOptions<TCode extends string> {
28
+ /** Stable SCREAMING_SNAKE identifier; also becomes `error.name`. */
29
+ code: TCode;
30
+ /** Technical message for logs and debugging, never for clients. */
31
+ message: string;
32
+ /** Optional underlying error preserved in the cause chain. */
33
+ cause?: unknown;
34
+ /** Whether retrying the failed operation can succeed. Default `false`. */
35
+ retryable?: boolean;
36
+ }
37
+ /**
38
+ * Abstract base for **domain-invariant violations**. Domain methods
39
+ * (aggregates, entity validation hooks, value-object constructors)
40
+ * throw `DomainError`-derived exceptions when a business rule is
41
+ * violated. Consumers derive their own concrete errors (e.g.
42
+ * `class OrderAlreadyShippedError extends DomainError<"ORDER_ALREADY_SHIPPED">`)
43
+ * for `instanceof`-style catching at the App-Service boundary, where
44
+ * they typically map to HTTP 400 / business-rule responses.
45
+ *
46
+ * The library itself ships no business-rule `DomainError` subclass: the
47
+ * kit can't know your invariants. (The domain-state-machine module's
48
+ * transition errors are the structural exception.)
49
+ *
50
+ * The `category` is fixed to `"DOMAIN"` and `retryable` defaults to
51
+ * `false`, so a subclass supplies only its `code` and `message`:
52
+ *
53
+ * ```ts
54
+ * class OrderAlreadyShippedError extends DomainError<"ORDER_ALREADY_SHIPPED"> {
55
+ * constructor(orderId: string) {
56
+ * super({
57
+ * code: "ORDER_ALREADY_SHIPPED",
58
+ * message: `Order ${orderId} has already been shipped`,
59
+ * });
60
+ * }
61
+ * }
62
+ * ```
63
+ */
64
+ declare abstract class DomainError<TCode extends string = string> extends StructuredError<TCode, "DOMAIN"> {
65
+ protected constructor(options: KitErrorOptions<TCode>);
66
+ }
67
+ /**
68
+ * Internal base for the kit's crash-loud **WIRING** family: deterministic
69
+ * programming/configuration bugs that must fail the operation loudly and
70
+ * never be absorbed by generic domain or infrastructure handlers. One
71
+ * implementation of the `{ category: "WIRING", retryable: false }` shape
72
+ * so the family cannot drift. Exported for the kit's own modules only;
73
+ * not part of the package entries.
74
+ */
75
+ declare abstract class KitWiringError<TCode extends string> extends StructuredError<TCode, "WIRING"> {
76
+ protected constructor(code: TCode, message: string, cause?: unknown);
77
+ }
78
+ /**
79
+ * Abstract base for **infrastructure / persistence failures** that the
80
+ * App-Service can recover from: typically by retrying, by returning
81
+ * HTTP 404 / 409, or by surfacing a "please try again" UX. These are
82
+ * not domain-invariant violations (the business rules were not
83
+ * broken); they describe race conditions and missing rows at the
84
+ * storage boundary.
85
+ *
86
+ * The `category` is fixed to `"INFRASTRUCTURE"`; `retryable` defaults
87
+ * to `false` (opt in per subclass, see {@link ConcurrencyConflictError}).
88
+ *
89
+ * Library-internal concrete subclasses: {@link AggregateNotFoundError},
90
+ * {@link ConcurrencyConflictError}, {@link DuplicateAggregateError},
91
+ * plus the unit-of-work lifecycle wrappers `CommitError` and
92
+ * `RollbackError` (in `src/app/unit-of-work.ts`).
93
+ */
94
+ declare abstract class InfrastructureError<TCode extends string = string> extends StructuredError<TCode, "INFRASTRUCTURE"> {
95
+ protected constructor(options: KitErrorOptions<TCode>);
96
+ }
97
+ /**
98
+ * Copy-safe membership check for the kit's domain-error family.
99
+ *
100
+ * `instanceof` is false for an error constructed by another loaded copy of
101
+ * the kit (a separately installed adapter package, a CJS/ESM dual load), so
102
+ * kit boundaries that route by error family fall back to the structural
103
+ * `category` field, the stable cross-copy contract.
104
+ */
105
+ declare function isDomainErrorLike(value: unknown): value is DomainError;
106
+ /**
107
+ * Copy-safe membership check for the kit's infrastructure-error family.
108
+ * Same rationale as {@link isDomainErrorLike}.
109
+ */
110
+ declare function isInfrastructureErrorLike(value: unknown): value is InfrastructureError;
111
+ /** Options bag for {@link InMemoryCapacityExceededError}. */
112
+ interface InMemoryCapacityExceededErrorOptions {
113
+ /** Concrete reference adapter whose configured capacity was exhausted. */
114
+ readonly store: string;
115
+ /** Bounded collection or logical resource, such as `events` or `sources`. */
116
+ readonly resource: string;
117
+ /** Configured maximum number of retained records. */
118
+ readonly limit: number;
119
+ /** Records retained before the rejected operation. */
120
+ readonly current: number;
121
+ /** New records the rejected operation would have retained. */
122
+ readonly attempted: number;
123
+ }
124
+ /**
125
+ * A finite-capacity in-memory reference adapter rejected new state before
126
+ * mutation. Existing records remain usable; callers must release explicit
127
+ * lifecycle state, increase the configured limit, or switch to a durable
128
+ * adapter. The error is not retryable without one of those external changes.
129
+ */
130
+ declare class InMemoryCapacityExceededError extends InfrastructureError<"IN_MEMORY_CAPACITY_EXCEEDED"> {
131
+ readonly store: string;
132
+ readonly resource: string;
133
+ readonly limit: number;
134
+ readonly current: number;
135
+ readonly attempted: number;
136
+ constructor(options: InMemoryCapacityExceededErrorOptions);
137
+ }
138
+ /**
139
+ * Thrown when event dispatch reaches a type with no own handler registration.
140
+ * This covers `EventSourcedAggregate.apply()` and the exhaustive
141
+ * `projectionFromHandlers` helper: the declared event union and its handler map
142
+ * disagree at runtime, which is a programming / configuration bug rather than
143
+ * a domain or infrastructure failure.
144
+ *
145
+ * Deliberately **not** on `DomainError` or `InfrastructureError`:
146
+ * a generic `catch (e instanceof DomainError)` handler at the App
147
+ * layer must not mask a forgotten handler; this should crash loud and
148
+ * fail the calling Use Case so the bug surfaces in development. The
149
+ * replay through `loadFromHistory` also lets it propagate uncaught instead
150
+ * of wrapping it in `Result.Err`.
151
+ *
152
+ * Use `isBaseError(e)` from `@shirudo/base-error` to detect
153
+ * "any structured error from the kit or any other BaseError-using
154
+ * library" at the App boundary.
155
+ */
156
+ declare class MissingHandlerError extends KitWiringError<"MISSING_HANDLER"> {
157
+ readonly eventType: string;
158
+ constructor(eventType: string, cause?: unknown);
159
+ }
160
+ /**
161
+ * Thrown by `Projector.project` when an event cannot be projected
162
+ * safely because its cursor is missing or malformed, or its aggregate
163
+ * address is absent. Applying such an event would break idempotency, so
164
+ * the batch fails. Events written by `withCommit` carry the complete
165
+ * cursor automatically; other sources compose a gap-proof committed-event
166
+ * envelope. A well-formed cursor that does not continue the stored chain
167
+ * instead throws {@link ProjectionGapError}.
168
+ *
169
+ * A wiring error, not a `DomainError`: see {@link MissingHandlerError}
170
+ * for the rationale of crashing loud at the App layer.
171
+ */
172
+ declare class UnprojectableEventError extends KitWiringError<"UNPROJECTABLE_EVENT"> {
173
+ readonly projection: string;
174
+ readonly eventId: string;
175
+ constructor(projection: string, eventId: string, reason: string, cause?: unknown);
176
+ }
177
+ /**
178
+ * Thrown when a valid projection cursor does not continue the stored
179
+ * per-aggregate chain. This is an infrastructure/delivery failure: an
180
+ * event or commit is missing, commonly because a partition reordered or
181
+ * dead-lettered it. The projector does not apply the later event and the
182
+ * checkpoint stays put until the missing history is replayed or the
183
+ * projection is rebuilt.
184
+ */
185
+ declare class ProjectionGapError extends InfrastructureError<"PROJECTION_GAP"> {
186
+ readonly projection: string;
187
+ readonly eventId: string;
188
+ readonly previousPosition: string;
189
+ readonly receivedPosition: string;
190
+ constructor(projection: string, eventId: string, previousPosition: string, receivedPosition: string);
191
+ }
192
+ /**
193
+ * Thrown when one batch delivers previously unseen positions of the same
194
+ * aggregate in descending order. Unlike {@link ProjectionGapError}, this is
195
+ * direct proof that the feed violated its per-aggregate ordering contract;
196
+ * no missing-history inference is needed. Positions already covered by the
197
+ * checkpoint at batch start and exact receipts repeated inside the batch
198
+ * remain valid redeliveries and do not trip this diagnostic guard.
199
+ */
200
+ declare class ProjectionOrderViolationError extends InfrastructureError<"PROJECTION_ORDER_VIOLATION"> {
201
+ readonly projection: string;
202
+ readonly eventId: string;
203
+ readonly previousReceivedPosition: string;
204
+ readonly receivedPosition: string;
205
+ constructor(projection: string, eventId: string, previousReceivedPosition: string, receivedPosition: string);
206
+ }
207
+ /**
208
+ * Thrown when a source maps different event identities to one position, either
209
+ * inside the current batch or at the position stored as the projection's
210
+ * watermark. The checkpoint retains the identity of that one last-applied
211
+ * event, so the durable collision is provable without keeping an unbounded
212
+ * processed-event ledger. Positions behind the watermark remain governed by
213
+ * the source's one-logical-event-per-position contract.
214
+ */
215
+ declare class ProjectionIdentityViolationError extends InfrastructureError<"PROJECTION_IDENTITY_VIOLATION"> {
216
+ readonly projection: string;
217
+ readonly eventId: string;
218
+ readonly recordedEventId: string;
219
+ readonly position: string;
220
+ constructor(projection: string, eventId: string, recordedEventId: string, position: string);
221
+ }
222
+ /**
223
+ * Thrown when one logical projection position keeps its event identity but its
224
+ * commit-boundary receipt changes. `commitSize` and
225
+ * `previousEventfulAggregateVersion` are part of the continuity proof, so a
226
+ * source must keep them immutable just like the eventId. Accepting a
227
+ * contradictory redelivery could hide an incomplete commit or predecessor.
228
+ */
229
+ declare class ProjectionReceiptViolationError extends InfrastructureError<"PROJECTION_RECEIPT_VIOLATION"> {
230
+ readonly projection: string;
231
+ readonly eventId: string;
232
+ readonly recordedReceipt: string;
233
+ readonly receivedReceipt: string;
234
+ constructor(projection: string, eventId: string, recordedReceipt: string, receivedReceipt: string);
235
+ }
236
+ /** A malformed or non-JSON-safe message at an integration boundary. */
237
+ declare class InvalidIntegrationMessageError extends InfrastructureError<"INVALID_INTEGRATION_MESSAGE"> {
238
+ readonly path: string;
239
+ readonly reason: string;
240
+ constructor(path: string, reason: string, cause?: unknown);
241
+ }
242
+ /** A malformed or non-JSON-safe command selected for durable delivery. */
243
+ declare class InvalidCommandMessageError extends InfrastructureError<"INVALID_COMMAND_MESSAGE"> {
244
+ readonly path: string;
245
+ readonly reason: string;
246
+ constructor(path: string, reason: string, cause?: unknown);
247
+ }
248
+ /**
249
+ * Thrown by `Entity` (constructor and `setState`) and by the event
250
+ * metadata helpers (`createDomainEvent`'s `options.metadata`,
251
+ * `mergeMetadata`, `copyMetadata`) when the value carries an own
252
+ * `"__proto__"` data key:
253
+ * the shape `JSON.parse` produces for hostile DB rows or request bodies
254
+ * handed to reconstitute factories. Such a key can never be legitimate
255
+ * domain state; accepting it would hand a prototype-pollution payload to
256
+ * every downstream consumer that copies the state through `[[Set]]`
257
+ * (`Object.assign`, for-in assignment loops), and dropping it would be
258
+ * silent data mutation.
259
+ *
260
+ * Deliberately **not** a `DomainError` or `InfrastructureError` (same
261
+ * posture as {@link MissingHandlerError}): untrusted input reaching the
262
+ * domain layer unvalidated is a boundary bug, and a generic
263
+ * business-rule handler must not absorb it. Validate and strip untrusted
264
+ * input at the application edge; model genuinely arbitrary keys with a
265
+ * `Map`, not a plain object.
266
+ */
267
+ declare class HostileStateKeyError extends KitWiringError<"HOSTILE_STATE_KEY"> {
268
+ readonly key: string;
269
+ constructor(key: string, subject?: string);
270
+ }
271
+ /**
272
+ * Thrown by `EventSourcedAggregate.loadFromHistory` when the replay target
273
+ * carries unflushed `pendingEvents`. Replaying persisted facts onto that
274
+ * instance would advance the version underneath decisions made against an
275
+ * older state and could later claim history the stream does not carry.
276
+ *
277
+ * Deliberately **not** a `DomainError` or `InfrastructureError` (same
278
+ * posture as {@link MissingHandlerError}): a deterministic programming
279
+ * bug in how the aggregate was constructed before the restore. It
280
+ * propagates as a throw instead of riding the replay methods' `Result`
281
+ * channel, so a generic corrupted-stream handler cannot absorb it.
282
+ * Reconstitution belongs on a bare instance: construct the aggregate
283
+ * without factory-recorded events or prior mutations, then restore.
284
+ *
285
+ * Each throw site carries the safe remedy in its `reason`. Persistence
286
+ * lifecycle state is intentionally not mutable through the aggregate API:
287
+ * commit an actually saved instance through application orchestration, or
288
+ * discard a dirty instance and replay into a fresh one.
289
+ */
290
+ declare class UnreplayableAggregateError extends KitWiringError<"UNREPLAYABLE_AGGREGATE"> {
291
+ readonly aggregateId: string;
292
+ constructor(aggregateId: string, reason: string);
293
+ }
294
+ /**
295
+ * Thrown by `EventSourcedAggregate.apply()` when a NEW event carries an
296
+ * `aggregateId` or `aggregateType` naming a different aggregate: a
297
+ * deterministic programming bug at the call site (a hand-built or
298
+ * copied event addressed elsewhere), caught before the event can be
299
+ * recorded and poison the own stream. Events with MISSING address
300
+ * fields do not trip this: `apply()` stamps them from the aggregate,
301
+ * the same guarantee `createEvent` gives. A wiring error, distinct
302
+ * from {@link ForeignEventError} on purpose: a wrong new event is a
303
+ * bug in today's code, a wrong PERSISTED row is corrupted or miswired
304
+ * infrastructure, and handlers for one must not absorb the other.
305
+ */
306
+ declare class MisaddressedEventError extends KitWiringError<"MISADDRESSED_EVENT"> {
307
+ readonly expectedAggregateId: string;
308
+ readonly expectedAggregateType: string;
309
+ readonly eventType: string;
310
+ readonly actualAggregateId?: string | undefined;
311
+ readonly actualAggregateType?: string | undefined;
312
+ constructor(expectedAggregateId: string, expectedAggregateType: string, eventType: string, actualAggregateId?: string | undefined, actualAggregateType?: string | undefined);
313
+ }
314
+ /**
315
+ * The structural-integrity rejection for a stored snapshot. A consumer's
316
+ * adapter-owned `SnapshotModel` may throw it from migration or reconstitution
317
+ * when the blob could not have been produced by any version of the model
318
+ * (missing fields, impossible types, truncated data). An
319
+ * `InfrastructureError`, because corrupted persistence is a storage
320
+ * problem, never a business rejection; it is nevertheless RECOVERABLE
321
+ * by design: the repository catches it, discards the derived snapshot, and
322
+ * refolds from the authoritative event stream.
323
+ */
324
+ declare class SnapshotCorruptedError extends InfrastructureError<"SNAPSHOT_CORRUPTED"> {
325
+ constructor(message: string, cause?: unknown);
326
+ }
327
+ /**
328
+ * Thrown when an event reaches the aggregate's recording paths
329
+ * (`apply`, `commit`, `addDomainEvent`) without having been minted by
330
+ * the kit's constructors: `createDomainEvent`,
331
+ * `createDomainEventFromFacts`, `createUncommittedDomainEvent`, or aggregate
332
+ * event helpers
333
+ * deep-freeze the event and defensively copy payload and metadata,
334
+ * and register the result in an internal, unforgeable mint marker.
335
+ * Anything else (a hand-rolled literal, a shallow-frozen copy with
336
+ * mutable nested data) is rejected: a mutable event recorded next to
337
+ * a state change can silently diverge from it afterwards. A wiring
338
+ * error: deterministic bug at the call site, the remedy is minting
339
+ * through the constructors.
340
+ */
341
+ declare class UnmintedEventError extends KitWiringError<"UNMINTED_EVENT"> {
342
+ constructor(eventType: string);
343
+ }
344
+ /**
345
+ * Thrown by `recordPendingEvents` when the aggregate's pending-event list
346
+ * changes while its events are being stamped: a stamp provider that
347
+ * directly or transitively triggers a new decision on the same aggregate
348
+ * would otherwise have that decision silently discarded when recording
349
+ * replaces the pending list. Recording is atomic: when this guard fires,
350
+ * every decision (including the re-entrant one) remains unrecorded. A
351
+ * wiring error: deterministic bug at the call site, the remedy is keeping
352
+ * stamp providers free of domain decisions.
353
+ */
354
+ declare class ReentrantEventRecordingError extends KitWiringError<"REENTRANT_EVENT_RECORDING"> {
355
+ constructor(aggregateId: string);
356
+ }
357
+ /**
358
+ * Thrown by `recordPendingEvents` when two events in one aggregate's pending
359
+ * batch carry the same `eventId`: a stamp provider that returns one reused
360
+ * stamp (or repeats an explicit id) would otherwise mint two distinct facts
361
+ * sharing one identity, and downstream idempotent consumers keyed on
362
+ * `eventId` silently drop one of them. A wiring error: deterministic bug in
363
+ * the stamp provider, the remedy is one fresh identity per decision.
364
+ */
365
+ declare class DuplicateEventIdError extends KitWiringError<"DUPLICATE_EVENT_ID"> {
366
+ /** The identity two pending events would have shared. */
367
+ readonly eventId: string;
368
+ constructor(aggregateId: string, /** The identity two pending events would have shared. */eventId: string);
369
+ }
370
+ /**
371
+ * Thrown by persisted-event consumers (including `loadFromHistory` and
372
+ * `Projector`) when an event carries an
373
+ * `aggregateId` or `aggregateType` that names a different aggregate:
374
+ * the persisted row belongs to someone else (a miswired stream read,
375
+ * ids colliding across aggregate types, a corrupted store). An
376
+ * `InfrastructureError`, NOT a `DomainError` (same posture as
377
+ * {@link SnapshotSchemaMismatchError}): a wrong address is data
378
+ * corruption or wiring, never an expected business rejection, so it
379
+ * must not be absorbed by generic domain error handling or presented
380
+ * as a 4xx. It therefore PROPAGATES as a throw through the replay
381
+ * methods' `Result` contract (which reserves `Err` for `DomainError`),
382
+ * after the usual all-or-nothing rollback. History events without the
383
+ * optional address fields pass unchecked (the fields are optional on
384
+ * the event shape); new events are covered by
385
+ * {@link MisaddressedEventError}.
386
+ */
387
+ declare class ForeignEventError extends InfrastructureError<"FOREIGN_EVENT"> {
388
+ readonly expectedAggregateId: string;
389
+ readonly expectedAggregateType: string;
390
+ readonly eventType: string;
391
+ readonly actualAggregateId?: string | undefined;
392
+ readonly actualAggregateType?: string | undefined;
393
+ constructor(expectedAggregateId: string, expectedAggregateType: string, eventType: string, actualAggregateId?: string | undefined, actualAggregateType?: string | undefined);
394
+ }
395
+ /** Constructor options for {@link NonProgressingEventStreamPageError}. */
396
+ interface NonProgressingEventStreamPageErrorOptions {
397
+ readonly aggregateType: string;
398
+ readonly aggregateId: string;
399
+ /** Exclusive continuation cursor supplied to `EventStore.readStream`. */
400
+ readonly fromVersion: number;
401
+ /** Pinned inclusive stream version the replay still has to reach. */
402
+ readonly targetVersion: number;
403
+ }
404
+ /**
405
+ * Thrown by a paged EventStore consumer when `readStream` returns no events
406
+ * even though its continuation cursor has not reached the pinned target.
407
+ * Such a page cannot advance and violates the EventStore port contract; a
408
+ * replay loop that merely continued would spin forever.
409
+ *
410
+ * This is a non-retryable infrastructure error: the persistence adapter
411
+ * deterministically contradicted its port contract, so retrying the same read
412
+ * is not a recovery policy. Run `createEventStoreContractTests` against the
413
+ * adapter and fix its windowing/continuation implementation.
414
+ */
415
+ declare class NonProgressingEventStreamPageError extends InfrastructureError<"NON_PROGRESSING_EVENT_STREAM_PAGE"> {
416
+ readonly aggregateType: string;
417
+ readonly aggregateId: string;
418
+ readonly fromVersion: number;
419
+ readonly targetVersion: number;
420
+ constructor(options: NonProgressingEventStreamPageErrorOptions);
421
+ }
422
+ /**
423
+ * Thrown when an event harvested from an aggregate cannot be safely composed
424
+ * into a commit envelope, or when an outbox can prove that accepting a
425
+ * candidate would violate its event identity/source chain. Harvest failures
426
+ * include missing `aggregateId` / `aggregateType` (downstream routing would
427
+ * break), or an
428
+ * eventful persisted aggregate did not advance its version (two commits
429
+ * would receive the same source position). These programming bugs are
430
+ * deterministic and fail identically on every retry.
431
+ *
432
+ * Deliberately **not** an {@link InfrastructureError} (same reasoning as
433
+ * {@link MissingHandlerError}): this is a deterministic programming error,
434
+ * not a transient storage failure. A `catch (e instanceof InfrastructureError)`
435
+ * retry handler, or a retrying `TransactionScope`, must NOT mask it or loop on
436
+ * it forever; it should crash loud so the caller misuse surfaces in
437
+ * development. This is why `withCommit` throws it directly and
438
+ * `UnitOfWork.run` passes it through unchanged instead of wrapping it in
439
+ * `CommitError`.
440
+ */
441
+ declare class EventHarvestError extends KitWiringError<"EVENT_HARVEST_FAILED"> {
442
+ /** The `type` of the offending event, for programmatic routing. */
443
+ readonly eventType?: string | undefined;
444
+ constructor(message: string, /** The `type` of the offending event, for programmatic routing. */eventType?: string | undefined);
445
+ }
446
+ /** Constructor options for {@link UnregisteredHandlerError}. */
447
+ interface UnregisteredHandlerErrorOptions {
448
+ /** Which bus rejected the dispatch. */
449
+ readonly busKind: "command" | "query";
450
+ /** The message type no handler was registered for. */
451
+ readonly messageType: string;
452
+ }
453
+ /**
454
+ * Produced by the in-memory `CommandBus` / `QueryBus` when a message is
455
+ * dispatched for a type no handler was registered under: a wiring bug
456
+ * (typo in the type string, missing `register` call at bootstrap), not
457
+ * a domain or infrastructure failure.
458
+ *
459
+ * Carries the `WIRING` category (same crash-loud family as
460
+ * {@link MissingHandlerError}), and since v3 it is THROWN by `execute`
461
+ * and `executeUnsafe` alike, never delivered through the error channel:
462
+ * the channel carries expected failures a registered handler produced,
463
+ * and a generic err-branch must not absorb a mis-wired bus. Catch it
464
+ * only at a boundary that turns bugs into 500s.
465
+ */
466
+ declare class UnregisteredHandlerError extends KitWiringError<"UNREGISTERED_HANDLER"> {
467
+ readonly busKind: "command" | "query";
468
+ readonly messageType: string;
469
+ constructor(options: UnregisteredHandlerErrorOptions);
470
+ }
471
+ /** Constructor options for {@link DuplicateHandlerRegistrationError}. */
472
+ interface DuplicateHandlerRegistrationErrorOptions {
473
+ /** Which bus rejected the registration. */
474
+ readonly busKind: "command" | "query";
475
+ /** The message type a handler was already registered for. */
476
+ readonly messageType: string;
477
+ }
478
+ /**
479
+ * Produced by `CommandBus.register` / `QueryBus.register` when a handler
480
+ * is registered for a type that already has one: silent replacement would
481
+ * turn the first handler into dead code with no signal, so the wiring bug
482
+ * surfaces at registration time. Same crash-loud family as
483
+ * {@link UnregisteredHandlerError}; catch it only at a boundary that
484
+ * turns bugs into 500s.
485
+ */
486
+ declare class DuplicateHandlerRegistrationError extends KitWiringError<"DUPLICATE_HANDLER_REGISTRATION"> {
487
+ readonly busKind: "command" | "query";
488
+ readonly messageType: string;
489
+ constructor(options: DuplicateHandlerRegistrationErrorOptions);
490
+ }
491
+ /** Constructor options for {@link ErrorMapperFailedError}. */
492
+ interface ErrorMapperFailedErrorOptions {
493
+ /** Which bus was mapping the failure. */
494
+ readonly busKind: "command" | "query";
495
+ /** The registered handler's ORIGINAL failure (also set as `cause`). */
496
+ readonly handlerError: unknown;
497
+ /** The mapper failure or invalid-decision diagnostic. */
498
+ readonly mapperError: unknown;
499
+ }
500
+ /**
501
+ * Produced by the in-memory `CommandBus` / `QueryBus` when the configured
502
+ * `mapExpectedError` policy fails while classifying a registered handler's
503
+ * failure, either by throwing or by returning an invalid decision. A broken
504
+ * mapper is a wiring bug: letting its failure propagate bare would
505
+ * replace the handler's original failure entirely, and the rest of the
506
+ * kit is fastidious about never letting a secondary failure mask the
507
+ * primary one (`RollbackError.rollbackCause`, the neutralized observers).
508
+ *
509
+ * The handler's original failure is preserved as `cause` (so cause-chain
510
+ * walks, retryability checks, and error-type mapping keep working) and
511
+ * the mapper's own failure rides along as {@link mapperCause}.
512
+ *
513
+ * Carries the `WIRING` category (same crash-loud family as
514
+ * {@link MissingHandlerError} and {@link UnregisteredHandlerError}): it is
515
+ * thrown, never delivered through the error channel.
516
+ */
517
+ declare class ErrorMapperFailedError extends KitWiringError<"ERROR_MAPPER_FAILED"> {
518
+ readonly busKind: "command" | "query";
519
+ /** The mapper failure or invalid-decision diagnostic. */
520
+ readonly mapperCause: unknown;
521
+ constructor(options: ErrorMapperFailedErrorOptions);
522
+ }
523
+ /**
524
+ * Thrown at the end of a `UnitOfWork.run` when an aggregate that was
525
+ * loaded into the identity map changed but no `update` intent was registered.
526
+ * Without this guard the changed state or pending events would be silently
527
+ * dropped.
528
+ *
529
+ * Deliberately **not** an `InfrastructureError` (same posture as
530
+ * {@link MissingHandlerError}): a programming bug that must crash loud,
531
+ * not be absorbed by a generic infrastructure-error handler. The throw
532
+ * happens inside the transaction, so the unit of work rolls back and
533
+ * leaves no partial state.
534
+ *
535
+ * **Scope of the guard.** A best-effort runtime safety net, not a proof.
536
+ * It sees aggregates that repository adapters register through
537
+ * `tracking.trackLoaded` and detects ordinary state changes through the version
538
+ * captured at load. The pending-event count remains a second guard for an
539
+ * invalid event-only mutation that did not advance the version. A freshly
540
+ * created aggregate that is never passed to `add` is invisible to the kit.
541
+ */
542
+ declare class UnenrolledChangesError extends KitWiringError<"UNENROLLED_CHANGES"> {
543
+ readonly aggregateId: string;
544
+ constructor(aggregateId: string);
545
+ }
546
+ /**
547
+ * Thrown when an aggregate removed within the current unit of work is added,
548
+ * updated, or tracked again in the same operation. Removal is final within an
549
+ * operation; writing afterwards would resurrect the row, which is always a
550
+ * use-case bug.
551
+ *
552
+ * Carries the `WIRING` category (same reasoning as
553
+ * {@link MissingHandlerError}): a programming bug that should crash
554
+ * loud, not be absorbed by a generic infrastructure-error handler.
555
+ */
556
+ declare class AggregateDeletedError extends KitWiringError<"AGGREGATE_DELETED"> {
557
+ readonly aggregateId: string;
558
+ constructor(aggregateId: string);
559
+ }
560
+ /**
561
+ * Thrown by `AggregatePersistence.getById()` when an aggregate with the
562
+ * given id does not exist. `InfrastructureError` because the storage
563
+ * boundary, not a business rule, decided the row is absent. Use the
564
+ * nullable variant `findById()` if "not found" is a valid outcome.
565
+ *
566
+ * Accepts an optional `cause` so a repository adapter can wrap a lower-level
567
+ * "row not found" or driver-level error without
568
+ * losing context. Cause-chain helpers (`getRootCause`,
569
+ * `findInCauseChain`) from `@shirudo/base-error` traverse the chain.
570
+ *
571
+ * Not retryable: retrying won't make the row appear.
572
+ */
573
+ interface AggregateNotFoundErrorOptions {
574
+ readonly aggregateType: string;
575
+ readonly id: string;
576
+ /** Optional lower-level error to preserve in the cause chain. */
577
+ readonly cause?: unknown;
578
+ }
579
+ declare class AggregateNotFoundError extends InfrastructureError<"AGGREGATE_NOT_FOUND"> {
580
+ readonly aggregateType: string;
581
+ readonly id: string;
582
+ constructor(options: AggregateNotFoundErrorOptions);
583
+ }
584
+ /**
585
+ * Thrown by a repository's `add()` flush when a row with the
586
+ * aggregate's id already exists (unique-constraint violation): two
587
+ * concurrent creators raced on the same business-derived id, or the
588
+ * id generator collided. Same delegation model as
589
+ * {@link ConcurrencyConflictError}: the kit ships the class, the
590
+ * consumer repository maps its driver's unique-violation signal to it
591
+ * instead of letting a raw driver error escape -
592
+ *
593
+ * - Postgres: SQLSTATE `23505` (`unique_violation`)
594
+ * - MySQL/MariaDB: errno `1062` (`ER_DUP_ENTRY`)
595
+ * - SQLite: `SQLITE_CONSTRAINT_UNIQUE` (extended code 2067)
596
+ *
597
+ * `InfrastructureError` because the storage boundary detects the
598
+ * collision. NOT retryable: re-running the same INSERT cannot succeed.
599
+ * The right reactions are domain decisions - map to HTTP 409, or for
600
+ * idempotency-key flows load the existing aggregate and treat the
601
+ * request as already-applied.
602
+ */
603
+ interface DuplicateAggregateErrorOptions {
604
+ readonly aggregateType: string;
605
+ readonly aggregateId: string;
606
+ /** Optional driver-level error to preserve in the cause chain. */
607
+ readonly cause?: unknown;
608
+ }
609
+ declare class DuplicateAggregateError extends InfrastructureError<"DUPLICATE_AGGREGATE"> {
610
+ readonly aggregateType: string;
611
+ readonly aggregateId: string;
612
+ constructor(options: DuplicateAggregateErrorOptions);
613
+ }
614
+ /**
615
+ * Thrown by `reconstituteAggregateFromSnapshot` when the stored snapshot
616
+ * carries a different schema version than its adapter-owned `SnapshotModel`
617
+ * and the model declares no `migrate` function. Without the check, a snapshot
618
+ * written against an older DTO shape would surface as an undefined-field crash on
619
+ * the first method call after a much later restore.
620
+ *
621
+ * `InfrastructureError` because the storage boundary served outdated
622
+ * data; the schema evolving past stored snapshots is an expected
623
+ * lifecycle event, not a programming bug. NOT retryable: the recovery
624
+ * is a code path, not a repeat. Add `migrate` to the snapshot model (upgrade
625
+ * old DTOs in place), or catch this error in the repository, discard the
626
+ * snapshot, and refold from the full event stream / reload from the source of
627
+ * truth.
628
+ */
629
+ interface SnapshotSchemaMismatchErrorOptions {
630
+ readonly aggregateType: string;
631
+ readonly aggregateId: string;
632
+ readonly expectedSchemaVersion: number;
633
+ readonly actualSchemaVersion: number;
634
+ }
635
+ declare class SnapshotSchemaMismatchError extends InfrastructureError<"SNAPSHOT_SCHEMA_MISMATCH"> {
636
+ readonly aggregateType: string;
637
+ readonly aggregateId: string;
638
+ readonly expectedSchemaVersion: number;
639
+ readonly actualSchemaVersion: number;
640
+ constructor(options: SnapshotSchemaMismatchErrorOptions);
641
+ }
642
+ /**
643
+ * Surfaced by a Unit-of-Work flush when the aggregate's expected version does
644
+ * not match the version currently persisted: i.e. another writer
645
+ * updated the aggregate concurrently. The canonical optimistic-
646
+ * concurrency signal; the App-Service typically reloads, re-applies
647
+ * the use case, and retries, or surfaces HTTP 409 to the caller.
648
+ *
649
+ * **Retry means a FRESH unit of work** (a new `UnitOfWork.run()` /
650
+ * `withCommit` invocation): reload, re-apply, and register `update` again. Do NOT catch this
651
+ * inside the same `run()` callback and continue: the failed aggregate
652
+ * is already enrolled (its events would be committed for a write that
653
+ * never happened) and the identity map still serves the same stale
654
+ * instance to any in-place "reload".
655
+ *
656
+ * `InfrastructureError` because the persistence layer (not a domain
657
+ * rule) detects the race. Marks itself as `retryable: true` so the
658
+ * `isRetryable` predicate from `@shirudo/base-error` picks it up.
659
+ */
660
+ interface ConcurrencyConflictErrorOptions {
661
+ readonly aggregateType: string;
662
+ readonly aggregateId: string;
663
+ readonly expectedVersion: number;
664
+ readonly actualVersion: number;
665
+ /** Optional driver-level error to preserve in the cause chain. */
666
+ readonly cause?: unknown;
667
+ }
668
+ declare class ConcurrencyConflictError extends InfrastructureError<"CONCURRENCY_CONFLICT"> {
669
+ readonly aggregateType: string;
670
+ readonly aggregateId: string;
671
+ readonly expectedVersion: number;
672
+ readonly actualVersion: number;
673
+ constructor(options: ConcurrencyConflictErrorOptions);
674
+ }
675
+ /**
676
+ * Options bag for {@link IdempotencyKeyReuseError}.
677
+ */
678
+ interface IdempotencyKeyReuseErrorOptions {
679
+ readonly key: string;
680
+ readonly storedFingerprint: string;
681
+ readonly receivedFingerprint: string;
682
+ /** Optional driver-level error to preserve in the cause chain. */
683
+ readonly cause?: unknown;
684
+ }
685
+ /**
686
+ * Thrown by `IdempotencyStore.claim()` when the same idempotency key
687
+ * arrives with a DIFFERENT command fingerprint than the one it was
688
+ * first claimed with: the caller is reusing a key for a different
689
+ * command. Replaying the stored outcome would answer a question that
690
+ * was never asked; rejecting is the only safe reaction.
691
+ *
692
+ * `InfrastructureError` because the store detects the collision, same
693
+ * delegation model as {@link DuplicateAggregateError}. NOT retryable:
694
+ * re-sending the same mismatched pair cannot succeed. Map it to an
695
+ * unprocessable/conflict application outcome.
696
+ */
697
+ declare class IdempotencyKeyReuseError extends InfrastructureError<"IDEMPOTENCY_KEY_REUSE"> {
698
+ readonly key: string;
699
+ readonly storedFingerprint: string;
700
+ readonly receivedFingerprint: string;
701
+ constructor(options: IdempotencyKeyReuseErrorOptions);
702
+ }
703
+ /** Options bag for {@link IdempotencyClaimLostError}. */
704
+ interface IdempotencyClaimLostErrorOptions {
705
+ readonly key: string;
706
+ readonly token: string;
707
+ /** Optional driver-level error to preserve in the cause chain. */
708
+ readonly cause?: unknown;
709
+ }
710
+ /**
711
+ * Thrown when a leased idempotency owner tries to renew, complete, or
712
+ * reconcile through a claim token that no longer owns the key. The usual
713
+ * cause is lease expiry followed by a successful takeover. The stale
714
+ * execution must abort before its transaction commits; retrying starts from
715
+ * a fresh claim or replays the winner.
716
+ */
717
+ declare class IdempotencyClaimLostError extends InfrastructureError<"IDEMPOTENCY_CLAIM_LOST"> {
718
+ readonly key: string;
719
+ readonly token: string;
720
+ constructor(options: IdempotencyClaimLostErrorOptions);
721
+ }
722
+ /**
723
+ * Options bag for {@link IdempotencyInFlightError}.
724
+ */
725
+ interface IdempotencyInFlightErrorOptions {
726
+ readonly key: string;
727
+ /** Optional driver-level error to preserve in the cause chain. */
728
+ readonly cause?: unknown;
729
+ }
730
+ /**
731
+ * Thrown by `IdempotencyStore.claim()` when the key is already claimed
732
+ * by an execution that has not completed yet: the first delivery of the
733
+ * command is still running (or crashed mid-flight on a
734
+ * non-transactional store). Retryable by design: a later retry either
735
+ * finds the completed outcome and replays it, or finds the claim
736
+ * released (rolled back) and executes fresh. `RetryingTransactionScope`
737
+ * picks this up through the `retryable` flag without extra wiring.
738
+ */
739
+ declare class IdempotencyInFlightError extends InfrastructureError<"IDEMPOTENCY_IN_FLIGHT"> {
740
+ readonly key: string;
741
+ constructor(options: IdempotencyInFlightErrorOptions);
742
+ }
743
+ /** Options bag for {@link IdempotencyReconciliationRequiredError}. */
744
+ interface IdempotencyReconciliationRequiredErrorOptions {
745
+ readonly key: string;
746
+ readonly fingerprint: string;
747
+ readonly token: string;
748
+ readonly expiredAt: string;
749
+ }
750
+ /**
751
+ * An expired staged outcome cannot be replayed or discarded until the
752
+ * application checks the authoritative write model. Immediate retry without
753
+ * that evidence cannot make progress, so this error is deliberately not
754
+ * marked retryable.
755
+ */
756
+ declare class IdempotencyReconciliationRequiredError extends InfrastructureError<"IDEMPOTENCY_RECONCILIATION_REQUIRED"> {
757
+ readonly key: string;
758
+ readonly fingerprint: string;
759
+ readonly token: string;
760
+ readonly expiredAt: string;
761
+ constructor(options: IdempotencyReconciliationRequiredErrorOptions);
762
+ }
763
+ /**
764
+ * Thrown by `IdempotencyStore.complete()` when no pending claim exists
765
+ * for the key: `complete` ran without a preceding successful `claim`
766
+ * in the same execution, or against a key whose claim was already
767
+ * completed or abandoned. Always a wiring bug in hand-rolled
768
+ * orchestration (`withIdempotentCommit` cannot produce it), hence the
769
+ * crash-loud category.
770
+ */
771
+ declare class IdempotencyCompletionWithoutClaimError extends KitWiringError<"IDEMPOTENCY_COMPLETED_WITHOUT_CLAIM"> {
772
+ readonly key: string;
773
+ constructor(key: string);
774
+ }
775
+ /**
776
+ * The closed union of every error code the kit itself can produce
777
+ * (consumer subclasses of {@link DomainError} / {@link InfrastructureError}
778
+ * add their own on top). Useful for building `switch` tables or
779
+ * base-error `matchError` cases that cover kit and consumer codes
780
+ * together, without importing anything from base-error.
781
+ */
782
+ type KitErrorCode = "AGGREGATE_DELETED" | "AGGREGATE_NOT_FOUND" | "AGGREGATE_TRACKING" | "COMMIT_FAILED" | "CONCURRENCY_CONFLICT" | "DOMAIN_TRANSITION_GUARD_REJECTED" | "DUPLICATE_AGGREGATE" | "DUPLICATE_EVENT_ID" | "DUPLICATE_HANDLER_REGISTRATION" | "ERROR_MAPPER_FAILED" | "EVENT_ADDRESS_INVALID" | "EVENT_HARVEST_FAILED" | "EVENT_ID_INVALID" | "EVENT_ID_REQUIRED" | "EVENT_OCCURRED_AT_INVALID" | "EVENT_OCCURRED_AT_REQUIRED" | "EVENT_SCHEMA_VERSION_INVALID" | "EVENT_TYPE_INVALID" | "FOREIGN_EVENT" | "HOSTILE_STATE_KEY" | "IDEMPOTENCY_CLAIM_LOST" | "IDEMPOTENCY_COMPLETED_WITHOUT_CLAIM" | "IDEMPOTENCY_IN_FLIGHT" | "IDEMPOTENCY_KEY_REUSE" | "IDEMPOTENCY_RECONCILIATION_REQUIRED" | "IN_MEMORY_CAPACITY_EXCEEDED" | "INVALID_DOMAIN_MACHINE_CONTEXT" | "INVALID_DOMAIN_MACHINE_DEFINITION" | "INVALID_DOMAIN_MACHINE_INPUT" | "INVALID_DOMAIN_MACHINE_SNAPSHOT" | "INVALID_DOMAIN_TRANSITION" | "INVALID_DOMAIN_TRANSITION_GUARD_RESULT" | "INVALID_DOMAIN_TRANSITION_RESULT" | "INVALID_COMMAND_MESSAGE" | "INVALID_INTEGRATION_MESSAGE" | "INVALID_MONEY" | "INVALID_REPOSITORY_ADAPTER" | "INVALID_REPOSITORY_DEFINITION" | "MISADDRESSED_EVENT" | "MISSING_HANDLER" | "MONEY_CURRENCY_MISMATCH" | "MONEY_PRECISION_LOSS" | "MONEY_SCALE_MISMATCH" | "NESTED_UNIT_OF_WORK" | "NON_PROGRESSING_EVENT_STREAM_PAGE" | "PROJECTION_GAP" | "PROJECTION_IDENTITY_VIOLATION" | "PROJECTION_ORDER_VIOLATION" | "PROJECTION_RECEIPT_VIOLATION" | "REENTRANT_DOMAIN_STATE_MACHINE_EVALUATION" | "REENTRANT_EVENT_RECORDING" | "REPOSITORY_ERROR_MAPPING_FAILED" | "ROLLBACK_FAILED" | "SNAPSHOT_CORRUPTED" | "SNAPSHOT_SCHEMA_MISMATCH" | "SNAPSHOT_TIME_INVALID" | "TRANSACTION_CLOSED" | "UNENROLLED_CHANGES" | "UNKNOWN_CURRENCY" | "UNMINTED_EVENT" | "UNPROJECTABLE_EVENT" | "UNREGISTERED_HANDLER" | "UNREPLAYABLE_AGGREGATE";
783
+ //#endregion
784
+ export { isInfrastructureErrorLike as $, InvalidIntegrationMessageError as A, ProjectionOrderViolationError as B, IdempotencyKeyReuseErrorOptions as C, InMemoryCapacityExceededErrorOptions as D, InMemoryCapacityExceededError as E, MissingHandlerError as F, SnapshotSchemaMismatchErrorOptions as G, ReentrantEventRecordingError as H, NonProgressingEventStreamPageError as I, UnprojectableEventError as J, UnenrolledChangesError as K, NonProgressingEventStreamPageErrorOptions as L, KitErrorOptions as M, KitWiringError as N, InfrastructureError as O, MisaddressedEventError as P, isDomainErrorLike as Q, ProjectionGapError as R, IdempotencyKeyReuseError as S, IdempotencyReconciliationRequiredErrorOptions as T, SnapshotCorruptedError as U, ProjectionReceiptViolationError as V, SnapshotSchemaMismatchError as W, UnregisteredHandlerErrorOptions as X, UnregisteredHandlerError as Y, UnreplayableAggregateError as Z, IdempotencyClaimLostError as _, ConcurrencyConflictErrorOptions as a, IdempotencyInFlightError as b, DuplicateAggregateErrorOptions as c, DuplicateHandlerRegistrationErrorOptions as d, ErrorMapperFailedError as f, HostileStateKeyError as g, ForeignEventError as h, ConcurrencyConflictError as i, KitErrorCode as j, InvalidCommandMessageError as k, DuplicateEventIdError as l, EventHarvestError as m, AggregateNotFoundError as n, DomainError as o, ErrorMapperFailedErrorOptions as p, UnmintedEventError as q, AggregateNotFoundErrorOptions as r, DuplicateAggregateError as s, AggregateDeletedError as t, DuplicateHandlerRegistrationError as u, IdempotencyClaimLostErrorOptions as v, IdempotencyReconciliationRequiredError as w, IdempotencyInFlightErrorOptions as x, IdempotencyCompletionWithoutClaimError as y, ProjectionIdentityViolationError as z };
785
+ //# sourceMappingURL=errors.d.ts.map