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