@shirudo/ddd-kit 1.3.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -63,8 +63,10 @@ interface IdGenerator<Tag extends string> {
63
63
  * subclass: the kit can't know your invariants.
64
64
  *
65
65
  * Extends `BaseError<Name>`; see `@shirudo/base-error` for the inherited
66
- * surface (timestamps, cause chains, `toJSON()`, `getUserMessage()`,
67
- * `isRetryable`, …).
66
+ * surface (timestamps, cause chains, `toJSON()`, `isRetryable`, …). For
67
+ * client-safe / localized messages, project errors through the opt-in
68
+ * `@shirudo/base-error/presentation` subpath at the boundary; the technical
69
+ * core deliberately carries no user-facing message.
68
70
  */
69
71
  declare abstract class DomainError<Name extends string = string> extends BaseError<Name> {
70
72
  }
@@ -188,10 +190,16 @@ declare class AggregateDeletedError extends BaseError<"AggregateDeletedError"> {
188
190
  *
189
191
  * Not retryable: retrying won't make the row appear.
190
192
  */
193
+ interface AggregateNotFoundErrorOptions {
194
+ readonly aggregateType: string;
195
+ readonly id: string;
196
+ /** Optional lower-level error to preserve in the cause chain. */
197
+ readonly cause?: unknown;
198
+ }
191
199
  declare class AggregateNotFoundError extends InfrastructureError<"AggregateNotFoundError"> {
192
200
  readonly aggregateType: string;
193
201
  readonly id: string;
194
- constructor(aggregateType: string, id: string, cause?: unknown);
202
+ constructor(options: AggregateNotFoundErrorOptions);
195
203
  }
196
204
  /**
197
205
  * Thrown by a repository's `save()` INSERT path when a row with the
@@ -212,10 +220,16 @@ declare class AggregateNotFoundError extends InfrastructureError<"AggregateNotFo
212
220
  * idempotency-key flows load the existing aggregate and treat the
213
221
  * request as already-applied.
214
222
  */
223
+ interface DuplicateAggregateErrorOptions {
224
+ readonly aggregateType: string;
225
+ readonly aggregateId: string;
226
+ /** Optional driver-level error to preserve in the cause chain. */
227
+ readonly cause?: unknown;
228
+ }
215
229
  declare class DuplicateAggregateError extends InfrastructureError<"DuplicateAggregateError"> {
216
230
  readonly aggregateType: string;
217
231
  readonly aggregateId: string;
218
- constructor(aggregateType: string, aggregateId: string, cause?: unknown);
232
+ constructor(options: DuplicateAggregateErrorOptions);
219
233
  }
220
234
  /**
221
235
  * Thrown by `IRepository.save()` when the aggregate's expected version
@@ -235,18 +249,26 @@ declare class DuplicateAggregateError extends InfrastructureError<"DuplicateAggr
235
249
  * rule) detects the race. Marks itself as `retryable: true` so the
236
250
  * `isRetryable` predicate from `@shirudo/base-error` picks it up.
237
251
  */
238
- declare class ConcurrencyConflictError extends InfrastructureError<"ConcurrencyConflictError"> {
252
+ interface ConcurrencyConflictErrorOptions {
239
253
  readonly aggregateType: string;
240
254
  readonly aggregateId: string;
241
255
  readonly expectedVersion: number;
242
256
  readonly actualVersion: number;
257
+ /** Optional driver-level error to preserve in the cause chain. */
258
+ readonly cause?: unknown;
259
+ }
260
+ declare class ConcurrencyConflictError extends InfrastructureError<"ConcurrencyConflictError"> {
243
261
  /**
244
262
  * Marks this error as retryable so `isRetryable(err)` returns
245
263
  * true. The canonical OCC pattern is to reload the aggregate, re-apply
246
264
  * the use case, and retry on this exception.
247
265
  */
248
266
  readonly retryable: true;
249
- constructor(aggregateType: string, aggregateId: string, expectedVersion: number, actualVersion: number, cause?: unknown);
267
+ readonly aggregateType: string;
268
+ readonly aggregateId: string;
269
+ readonly expectedVersion: number;
270
+ readonly actualVersion: number;
271
+ constructor(options: ConcurrencyConflictErrorOptions);
250
272
  }
251
273
 
252
274
  /**
@@ -263,9 +285,8 @@ declare class ConcurrencyConflictError extends InfrastructureError<"ConcurrencyC
263
285
  */
264
286
  type EventIdFactory = () => string;
265
287
  /**
266
- * Replaces the global event-id factory used by `createDomainEvent` and
267
- * `createDomainEventWithMetadata`. Call once during application bootstrap,
268
- * for example:
288
+ * Replaces the global event-id factory used by `createDomainEvent`. Call
289
+ * once during application bootstrap, for example:
269
290
  *
270
291
  * ```ts
271
292
  * import { ulid } from "ulid";
@@ -341,9 +362,8 @@ declare function resetEventIdFactory(): void;
341
362
  */
342
363
  type ClockFactory = () => Date;
343
364
  /**
344
- * Replaces the global clock factory used by `createDomainEvent` and
345
- * `createDomainEventWithMetadata`. Call once during application bootstrap
346
- * (or per-test in deterministic test suites):
365
+ * Replaces the global clock factory used by `createDomainEvent`. Call once
366
+ * during application bootstrap (or per-test in deterministic test suites):
347
367
  *
348
368
  * ```ts
349
369
  * import { setClockFactory } from "@shirudo/ddd-kit";
@@ -583,20 +603,6 @@ interface CreateDomainEventOptions {
583
603
  */
584
604
  declare function createDomainEvent<T extends string>(type: T, payload?: undefined, options?: CreateDomainEventOptions): DomainEvent<T, void>;
585
605
  declare function createDomainEvent<T extends string, P>(type: T, payload: P, options?: CreateDomainEventOptions): DomainEvent<T, P>;
586
- /**
587
- * Creates a domain event with metadata for traceability.
588
- * Convenience function for creating events with correlation and causation IDs.
589
- *
590
- * @example
591
- * ```typescript
592
- * const event = createDomainEventWithMetadata(
593
- * "OrderCreated",
594
- * { orderId: "123" },
595
- * { correlationId: "corr-123", causationId: "cmd-456", userId: "user-789" }
596
- * );
597
- * ```
598
- */
599
- declare function createDomainEventWithMetadata<T extends string, P>(type: T, payload: P, metadata: EventMetadata, options?: Omit<CreateDomainEventOptions, "metadata">): DomainEvent<T, P>;
600
606
  /**
601
607
  * Copies metadata from a source event to a new event.
602
608
  * Useful for maintaining correlation chains in event-driven architectures.
@@ -713,4 +719,4 @@ declare function sameVersion<TId extends Id<string>>(a: {
713
719
  version: Version;
714
720
  }): boolean;
715
721
 
716
- export { type AnyDomainEvent as A, type CreateDomainEventOptions as C, DomainError as D, type EventIdFactory as E, type IAggregateRoot as I, MissingHandlerError as M, UnenrolledChangesError as U, type Version as V, type Id as a, type AggregateSnapshot as b, type IEventSourcedAggregate as c, InfrastructureError as d, setEventIdFactory as e, type ClockFactory as f, setClockFactory as g, withClockFactory as h, resetClockFactory as i, type EventMetadata as j, type DomainEvent as k, createDomainEvent as l, createDomainEventWithMetadata as m, copyMetadata as n, mergeMetadata as o, EventHarvestError as p, AggregateDeletedError as q, resetEventIdFactory as r, sameVersion as s, AggregateNotFoundError as t, DuplicateAggregateError as u, ConcurrencyConflictError as v, withEventIdFactory as w, type IdGenerator as x };
722
+ export { type AnyDomainEvent as A, type CreateDomainEventOptions as C, DomainError as D, type EventIdFactory as E, type IAggregateRoot as I, MissingHandlerError as M, UnenrolledChangesError as U, type Version as V, type Id as a, type AggregateSnapshot as b, type IEventSourcedAggregate as c, InfrastructureError as d, setEventIdFactory as e, type ClockFactory as f, setClockFactory as g, withClockFactory as h, resetClockFactory as i, type EventMetadata as j, type DomainEvent as k, createDomainEvent as l, copyMetadata as m, mergeMetadata as n, EventHarvestError as o, AggregateDeletedError as p, type AggregateNotFoundErrorOptions as q, resetEventIdFactory as r, sameVersion as s, AggregateNotFoundError as t, type DuplicateAggregateErrorOptions as u, DuplicateAggregateError as v, withEventIdFactory as w, type ConcurrencyConflictErrorOptions as x, ConcurrencyConflictError as y, type IdGenerator as z };
package/dist/http.d.ts CHANGED
@@ -1,31 +1,54 @@
1
- import { ValidationError, ProblemDetailsOptions, ProblemDetails } from '@shirudo/base-error';
1
+ import { ValidationError } from '@shirudo/base-error';
2
+ import { ProblemDetailsExtensions, ProblemDetails } from '@shirudo/base-error/problem-details';
2
3
 
3
4
  /** Extension member that carries the collected field issues. */
4
5
  type ValidationProblemMember = "errors" | "invalid-params";
5
6
  /**
6
- * Options for {@link toProblemDetails}. Mirrors base-error's
7
- * `ProblemDetailsOptions` but takes over the `extensions` member to attach the
8
- * collected field issues, and adds {@link member} to choose the wire key.
7
+ * Options for {@link toProblemDetails}: the standard RFC 9457 members the
8
+ * boundary may set, plus {@link member} to choose the wire key for the issues
9
+ * and {@link extensions} for extra public members merged alongside them.
9
10
  */
10
- interface ValidationProblemOptions extends Omit<ProblemDetailsOptions, "extensions"> {
11
+ interface ValidationProblemOptions {
12
+ /** URI reference identifying the problem type. Defaults to `"about:blank"`. */
13
+ type?: string;
14
+ /** Short, human-readable summary. Default `"Validation Failed"`. */
15
+ title?: string;
16
+ /** HTTP status code. Default `422`. */
17
+ status?: number;
18
+ /** Human-readable explanation specific to this occurrence. */
19
+ detail?: string;
20
+ /** URI reference identifying this specific occurrence. */
21
+ instance?: string;
11
22
  /**
12
23
  * Extension member that carries the field issues. Default `"errors"`
13
24
  * (`{ message, path, code?, pointer? }` entries). RFC 9457 does not
14
25
  * standardize a multi-error member; `errors` is the common convention.
15
26
  */
16
27
  member?: ValidationProblemMember;
17
- /** Extra public extension members merged alongside the issues. */
18
- extensions?: Record<string, unknown>;
28
+ /**
29
+ * Extra public extension members merged alongside the issues. JSON-safe
30
+ * by contract (RFC 9457 bodies must serialize); a trace id, for example,
31
+ * is passed here, not as a recognized top-level field.
32
+ */
33
+ extensions?: ProblemDetailsExtensions;
19
34
  }
20
35
  /**
21
36
  * Projects a base-error {@link ValidationError} to an RFC 9457 Problem Details
22
37
  * object with the collected field issues attached under an extension member.
38
+ * The return type is base-error's own
39
+ * {@link ProblemDetails} (from `@shirudo/base-error/problem-details`), so the
40
+ * RFC 9457 shape stays a single source of truth across the ecosystem.
23
41
  *
24
- * base-error is **safe by default**: `ValidationError.toProblemDetails()` does
25
- * not expose the issues on its own: they only cross to a client through the
26
- * `publicIssues()` whitelist. This helper performs that explicit projection and
42
+ * base-error is **safe by default**: the issues only cross to a client through
43
+ * the `publicIssues()` whitelist (`{ message, path, code?, pointer? }`, never
44
+ * raw validator extras). This helper performs that explicit projection and
27
45
  * applies sensible validation defaults (`422`, `"Validation Failed"`), so the
28
- * common boundary case is a one-liner instead of a footgun.
46
+ * common boundary case is a one-liner instead of a footgun. The full-fidelity
47
+ * issues remain available for observability via `error.toLogObject()`.
48
+ *
49
+ * For the general error-to-Problem-Details mapping (a public-code catalog with
50
+ * per-code `type` / `status`), use base-error's `defineProblemDetailsAdapter`
51
+ * over a `PublicErrorView`. This helper is the narrow validation shortcut.
29
52
  *
30
53
  * This is a presentation/transport concern and ships from the opt-in
31
54
  * `@shirudo/ddd-kit/http` entry point: the core kit stays transport-free.
@@ -37,10 +60,10 @@ interface ValidationProblemOptions extends Omit<ProblemDetailsOptions, "extensio
37
60
  * if (result.isErr()) {
38
61
  * return Response.json(toProblemDetails(result.error), { status: 422 });
39
62
  * }
40
- * // → { type, title: "Validation Failed", status: 422,
63
+ * // → { type: "about:blank", title: "Validation Failed", status: 422,
41
64
  * // errors: [{ message: "must be a valid email", path: ["email"], pointer: "email" }] }
42
65
  * ```
43
66
  */
44
- declare function toProblemDetails(error: ValidationError, options?: ValidationProblemOptions): ProblemDetails;
67
+ declare function toProblemDetails(error: ValidationError, options?: ValidationProblemOptions): ProblemDetails<never, ProblemDetailsExtensions>;
45
68
 
46
69
  export { type ValidationProblemMember, type ValidationProblemOptions, toProblemDetails };
package/dist/http.js CHANGED
@@ -3,13 +3,21 @@ var __name = (target, value) => __defProp(target, "name", { value, configurable:
3
3
 
4
4
  // src/http/problem-details.ts
5
5
  function toProblemDetails(error, options = {}) {
6
- const { member = "errors", extensions, ...rest } = options;
7
- return error.toProblemDetails({
8
- title: "Validation Failed",
9
- status: 422,
10
- ...rest,
11
- extensions: { ...extensions, [member]: error.publicIssues() }
12
- });
6
+ const {
7
+ member = "errors",
8
+ extensions,
9
+ type = "about:blank",
10
+ title = "Validation Failed",
11
+ status = 422,
12
+ detail,
13
+ instance
14
+ } = options;
15
+ const problem = { type, title, status };
16
+ if (detail !== void 0) problem.detail = detail;
17
+ if (instance !== void 0) problem.instance = instance;
18
+ if (extensions) Object.assign(problem, extensions);
19
+ problem[member] = error.publicIssues();
20
+ return problem;
13
21
  }
14
22
  __name(toProblemDetails, "toProblemDetails");
15
23
 
package/dist/http.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/http/problem-details.ts"],"names":[],"mappings":";;;;AAkDO,SAAS,gBAAA,CACf,KAAA,EACA,OAAA,GAAoC,EAAC,EACpB;AACjB,EAAA,MAAM,EAAE,MAAA,GAAS,QAAA,EAAU,UAAA,EAAY,GAAG,MAAK,GAAI,OAAA;AACnD,EAAA,OAAO,MAAM,gBAAA,CAAiB;AAAA,IAC7B,KAAA,EAAO,mBAAA;AAAA,IACP,MAAA,EAAQ,GAAA;AAAA,IACR,GAAG,IAAA;AAAA,IACH,UAAA,EAAY,EAAE,GAAG,UAAA,EAAY,CAAC,MAAM,GAAG,KAAA,CAAM,YAAA,EAAa;AAAE,GAC5D,CAAA;AACF;AAXgB,MAAA,CAAA,gBAAA,EAAA,kBAAA,CAAA","file":"http.js","sourcesContent":["import type {\n\tProblemDetails,\n\tProblemDetailsOptions,\n\tValidationError,\n} from \"@shirudo/base-error\";\n\n/** Extension member that carries the collected field issues. */\nexport type ValidationProblemMember = \"errors\" | \"invalid-params\";\n\n/**\n * Options for {@link toProblemDetails}. Mirrors base-error's\n * `ProblemDetailsOptions` but takes over the `extensions` member to attach the\n * collected field issues, and adds {@link member} to choose the wire key.\n */\nexport interface ValidationProblemOptions\n\textends Omit<ProblemDetailsOptions, \"extensions\"> {\n\t/**\n\t * Extension member that carries the field issues. Default `\"errors\"`\n\t * (`{ message, path, code?, pointer? }` entries). RFC 9457 does not\n\t * standardize a multi-error member; `errors` is the common convention.\n\t */\n\tmember?: ValidationProblemMember;\n\t/** Extra public extension members merged alongside the issues. */\n\textensions?: Record<string, unknown>;\n}\n\n/**\n * Projects a base-error {@link ValidationError} to an RFC 9457 Problem Details\n * object with the collected field issues attached under an extension member.\n *\n * base-error is **safe by default**: `ValidationError.toProblemDetails()` does\n * not expose the issues on its own: they only cross to a client through the\n * `publicIssues()` whitelist. This helper performs that explicit projection and\n * applies sensible validation defaults (`422`, `\"Validation Failed\"`), so the\n * common boundary case is a one-liner instead of a footgun.\n *\n * This is a presentation/transport concern and ships from the opt-in\n * `@shirudo/ddd-kit/http` entry point: the core kit stays transport-free.\n *\n * @example\n * ```ts\n * import { toProblemDetails } from \"@shirudo/ddd-kit/http\";\n *\n * if (result.isErr()) {\n * return Response.json(toProblemDetails(result.error), { status: 422 });\n * }\n * // → { type, title: \"Validation Failed\", status: 422,\n * // errors: [{ message: \"must be a valid email\", path: [\"email\"], pointer: \"email\" }] }\n * ```\n */\nexport function toProblemDetails(\n\terror: ValidationError,\n\toptions: ValidationProblemOptions = {},\n): ProblemDetails {\n\tconst { member = \"errors\", extensions, ...rest } = options;\n\treturn error.toProblemDetails({\n\t\ttitle: \"Validation Failed\",\n\t\tstatus: 422,\n\t\t...rest,\n\t\textensions: { ...extensions, [member]: error.publicIssues() },\n\t});\n}\n"]}
1
+ {"version":3,"sources":["../src/http/problem-details.ts"],"names":[],"mappings":";;;;AAuEO,SAAS,gBAAA,CACf,KAAA,EACA,OAAA,GAAoC,EAAC,EACa;AAClD,EAAA,MAAM;AAAA,IACL,MAAA,GAAS,QAAA;AAAA,IACT,UAAA;AAAA,IACA,IAAA,GAAO,aAAA;AAAA,IACP,KAAA,GAAQ,mBAAA;AAAA,IACR,MAAA,GAAS,GAAA;AAAA,IACT,MAAA;AAAA,IACA;AAAA,GACD,GAAI,OAAA;AAEJ,EAAA,MAAM,OAAA,GAAmC,EAAE,IAAA,EAAM,KAAA,EAAO,MAAA,EAAO;AAC/D,EAAA,IAAI,MAAA,KAAW,MAAA,EAAW,OAAA,CAAQ,MAAA,GAAS,MAAA;AAC3C,EAAA,IAAI,QAAA,KAAa,MAAA,EAAW,OAAA,CAAQ,QAAA,GAAW,QAAA;AAC/C,EAAA,IAAI,UAAA,EAAY,MAAA,CAAO,MAAA,CAAO,OAAA,EAAS,UAAU,CAAA;AAIjD,EAAA,OAAA,CAAQ,MAAM,CAAA,GAAI,KAAA,CAAM,YAAA,EAAa;AAErC,EAAA,OAAO,OAAA;AACR;AAxBgB,MAAA,CAAA,gBAAA,EAAA,kBAAA,CAAA","file":"http.js","sourcesContent":["import type { ValidationError } from \"@shirudo/base-error\";\nimport type {\n\tProblemDetails,\n\tProblemDetailsExtensions,\n} from \"@shirudo/base-error/problem-details\";\n\n/** Extension member that carries the collected field issues. */\nexport type ValidationProblemMember = \"errors\" | \"invalid-params\";\n\n/**\n * Options for {@link toProblemDetails}: the standard RFC 9457 members the\n * boundary may set, plus {@link member} to choose the wire key for the issues\n * and {@link extensions} for extra public members merged alongside them.\n */\nexport interface ValidationProblemOptions {\n\t/** URI reference identifying the problem type. Defaults to `\"about:blank\"`. */\n\ttype?: string;\n\t/** Short, human-readable summary. Default `\"Validation Failed\"`. */\n\ttitle?: string;\n\t/** HTTP status code. Default `422`. */\n\tstatus?: number;\n\t/** Human-readable explanation specific to this occurrence. */\n\tdetail?: string;\n\t/** URI reference identifying this specific occurrence. */\n\tinstance?: string;\n\t/**\n\t * Extension member that carries the field issues. Default `\"errors\"`\n\t * (`{ message, path, code?, pointer? }` entries). RFC 9457 does not\n\t * standardize a multi-error member; `errors` is the common convention.\n\t */\n\tmember?: ValidationProblemMember;\n\t/**\n\t * Extra public extension members merged alongside the issues. JSON-safe\n\t * by contract (RFC 9457 bodies must serialize); a trace id, for example,\n\t * is passed here, not as a recognized top-level field.\n\t */\n\textensions?: ProblemDetailsExtensions;\n}\n\n/**\n * Projects a base-error {@link ValidationError} to an RFC 9457 Problem Details\n * object with the collected field issues attached under an extension member.\n * The return type is base-error's own\n * {@link ProblemDetails} (from `@shirudo/base-error/problem-details`), so the\n * RFC 9457 shape stays a single source of truth across the ecosystem.\n *\n * base-error is **safe by default**: the issues only cross to a client through\n * the `publicIssues()` whitelist (`{ message, path, code?, pointer? }`, never\n * raw validator extras). This helper performs that explicit projection and\n * applies sensible validation defaults (`422`, `\"Validation Failed\"`), so the\n * common boundary case is a one-liner instead of a footgun. The full-fidelity\n * issues remain available for observability via `error.toLogObject()`.\n *\n * For the general error-to-Problem-Details mapping (a public-code catalog with\n * per-code `type` / `status`), use base-error's `defineProblemDetailsAdapter`\n * over a `PublicErrorView`. This helper is the narrow validation shortcut.\n *\n * This is a presentation/transport concern and ships from the opt-in\n * `@shirudo/ddd-kit/http` entry point: the core kit stays transport-free.\n *\n * @example\n * ```ts\n * import { toProblemDetails } from \"@shirudo/ddd-kit/http\";\n *\n * if (result.isErr()) {\n * return Response.json(toProblemDetails(result.error), { status: 422 });\n * }\n * // → { type: \"about:blank\", title: \"Validation Failed\", status: 422,\n * // errors: [{ message: \"must be a valid email\", path: [\"email\"], pointer: \"email\" }] }\n * ```\n */\nexport function toProblemDetails(\n\terror: ValidationError,\n\toptions: ValidationProblemOptions = {},\n): ProblemDetails<never, ProblemDetailsExtensions> {\n\tconst {\n\t\tmember = \"errors\",\n\t\textensions,\n\t\ttype = \"about:blank\",\n\t\ttitle = \"Validation Failed\",\n\t\tstatus = 422,\n\t\tdetail,\n\t\tinstance,\n\t} = options;\n\n\tconst problem: Record<string, unknown> = { type, title, status };\n\tif (detail !== undefined) problem.detail = detail;\n\tif (instance !== undefined) problem.instance = instance;\n\tif (extensions) Object.assign(problem, extensions);\n\t// `PublicIssue.path` is `ReadonlyArray<PropertyKey>`, so the issue array is\n\t// not statically a `ProblemDetailsJsonValue`; the wire form only ever\n\t// carries string/number path segments, so this is JSON-safe in practice.\n\tproblem[member] = error.publicIssues();\n\n\treturn problem as ProblemDetails<never, ProblemDetailsExtensions>;\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { a as Id, A as AnyDomainEvent, I as IAggregateRoot, V as Version, b as AggregateSnapshot, C as CreateDomainEventOptions, c as IEventSourcedAggregate, D as DomainError, d as InfrastructureError } from './aggregate-BGdgvqKh.js';
2
- export { q as AggregateDeletedError, t as AggregateNotFoundError, f as ClockFactory, v as ConcurrencyConflictError, k as DomainEvent, u as DuplicateAggregateError, p as EventHarvestError, E as EventIdFactory, j as EventMetadata, x as IdGenerator, M as MissingHandlerError, U as UnenrolledChangesError, n as copyMetadata, l as createDomainEvent, m as createDomainEventWithMetadata, o as mergeMetadata, i as resetClockFactory, r as resetEventIdFactory, s as sameVersion, g as setClockFactory, e as setEventIdFactory, h as withClockFactory, w as withEventIdFactory } from './aggregate-BGdgvqKh.js';
1
+ import { a as Id, A as AnyDomainEvent, I as IAggregateRoot, V as Version, b as AggregateSnapshot, C as CreateDomainEventOptions, c as IEventSourcedAggregate, D as DomainError, d as InfrastructureError } from './aggregate-CePTINEt.js';
2
+ export { p as AggregateDeletedError, t as AggregateNotFoundError, q as AggregateNotFoundErrorOptions, f as ClockFactory, y as ConcurrencyConflictError, x as ConcurrencyConflictErrorOptions, k as DomainEvent, v as DuplicateAggregateError, u as DuplicateAggregateErrorOptions, o as EventHarvestError, E as EventIdFactory, j as EventMetadata, z as IdGenerator, M as MissingHandlerError, U as UnenrolledChangesError, m as copyMetadata, l as createDomainEvent, n as mergeMetadata, i as resetClockFactory, r as resetEventIdFactory, s as sameVersion, g as setClockFactory, e as setEventIdFactory, h as withClockFactory, w as withEventIdFactory } from './aggregate-CePTINEt.js';
3
3
  import { Result } from '@shirudo/result';
4
4
  import { BaseError, ValidationError } from '@shirudo/base-error';
5
5
  import { DeepEqualExceptOptions } from './utils.js';
@@ -1006,6 +1006,8 @@ interface Command {
1006
1006
  *
1007
1007
  * @template C - The command type (must extend Command)
1008
1008
  * @template R - The result type
1009
+ * @template E - The error channel type. Defaults to `string`; widen it (e.g.
1010
+ * to a `DomainError` union) to carry typed failures through the bus.
1009
1011
  *
1010
1012
  * @example
1011
1013
  * ```typescript
@@ -1040,7 +1042,7 @@ interface Command {
1040
1042
  * });
1041
1043
  * ```
1042
1044
  */
1043
- type CommandHandler<C extends Command, R> = (cmd: C) => Promise<Result<R, string>>;
1045
+ type CommandHandler<C extends Command, R, E = string> = (cmd: C) => Promise<Result<R, E>>;
1044
1046
 
1045
1047
  /**
1046
1048
  * Type map for command types to their return types.
@@ -1059,6 +1061,30 @@ type CommandHandler<C extends Command, R> = (cmd: C) => Promise<Result<R, string
1059
1061
  * ```
1060
1062
  */
1061
1063
  type CommandTypeMap = Record<string, unknown>;
1064
+ /**
1065
+ * Construction options for {@link CommandBus}.
1066
+ *
1067
+ * @template E - The error channel type of the bus.
1068
+ */
1069
+ interface CommandBusOptions<E = string> {
1070
+ /**
1071
+ * Maps a thrown value (a handler that throws, or dispatch to an
1072
+ * unregistered command type) into the bus's error channel `E`. Defaults to
1073
+ * {@link describeThrown}, which renders any thrown value as a `string`.
1074
+ * base-error's `toStructuredError` fits this slot directly when `E` is a
1075
+ * `StructuredError`.
1076
+ */
1077
+ errorMapper?: (thrown: unknown) => E;
1078
+ }
1079
+ /**
1080
+ * Constructor arguments for {@link CommandBus}. When `E` is the default
1081
+ * `string`, options are optional (the built-in {@link describeThrown} mapper
1082
+ * applies). When `E` is widened, an `errorMapper` is required, so a typed
1083
+ * channel can never silently fall back to string values.
1084
+ */
1085
+ type CommandBusArgs<E> = [E] extends [string] ? [options?: CommandBusOptions<E>] : [options: CommandBusOptions<E> & {
1086
+ errorMapper: (thrown: unknown) => E;
1087
+ }];
1062
1088
  /**
1063
1089
  * Command Bus interface for dispatching commands to their handlers.
1064
1090
  * Provides a centralized way to execute commands with handler registration.
@@ -1083,18 +1109,18 @@ type CommandTypeMap = Record<string, unknown>;
1083
1109
  * // result: Result<unknown, string>
1084
1110
  * ```
1085
1111
  */
1086
- interface ICommandBus<TMap extends CommandTypeMap = CommandTypeMap> {
1112
+ interface ICommandBus<TMap extends CommandTypeMap = CommandTypeMap, E = string> {
1087
1113
  /**
1088
1114
  * Executes a command by dispatching it to the registered handler.
1089
1115
  * When a type map is provided, the return type is inferred from the command type.
1090
1116
  *
1091
1117
  * @param command - The command to execute
1092
- * @returns Result containing the success value or error message
1118
+ * @returns Result containing the success value or an error of type `E`
1093
1119
  */
1094
1120
  execute<C extends Command & {
1095
1121
  type: keyof TMap & string;
1096
- }>(command: C): Promise<Result<TMap[C["type"]], string>>;
1097
- execute<C extends Command, R>(command: C): Promise<Result<R, string>>;
1122
+ }>(command: C): Promise<Result<TMap[C["type"]], E>>;
1123
+ execute<C extends Command, R>(command: C): Promise<Result<R, E>>;
1098
1124
  /**
1099
1125
  * Registers a handler for a specific command type.
1100
1126
  *
@@ -1111,7 +1137,7 @@ interface ICommandBus<TMap extends CommandTypeMap = CommandTypeMap> {
1111
1137
  type: K;
1112
1138
  } = Command & {
1113
1139
  type: K;
1114
- }>(commandType: K, handler: CommandHandler<C, TMap[K]>): void;
1140
+ }>(commandType: K, handler: CommandHandler<C, TMap[K], E>): void;
1115
1141
  }
1116
1142
  /**
1117
1143
  * Simple in-memory command bus implementation.
@@ -1149,17 +1175,19 @@ interface ICommandBus<TMap extends CommandTypeMap = CommandTypeMap> {
1149
1175
  * const result = await bus.execute({ type: "CreateOrder", ... });
1150
1176
  * ```
1151
1177
  */
1152
- declare class CommandBus<TMap extends CommandTypeMap = CommandTypeMap> implements ICommandBus<TMap> {
1178
+ declare class CommandBus<TMap extends CommandTypeMap = CommandTypeMap, E = string> implements ICommandBus<TMap, E> {
1153
1179
  private readonly handlers;
1180
+ private readonly errorMapper;
1181
+ constructor(...args: CommandBusArgs<E>);
1154
1182
  register<K extends keyof TMap & string, C extends Command & {
1155
1183
  type: K;
1156
1184
  } = Command & {
1157
1185
  type: K;
1158
- }>(commandType: K, handler: CommandHandler<C, TMap[K]>): void;
1186
+ }>(commandType: K, handler: CommandHandler<C, TMap[K], E>): void;
1159
1187
  execute<C extends Command & {
1160
1188
  type: keyof TMap & string;
1161
- }>(command: C): Promise<Result<TMap[C["type"]], string>>;
1162
- execute<C extends Command, R>(command: C): Promise<Result<R, string>>;
1189
+ }>(command: C): Promise<Result<TMap[C["type"]], E>>;
1190
+ execute<C extends Command, R>(command: C): Promise<Result<R, E>>;
1163
1191
  }
1164
1192
 
1165
1193
  /**
@@ -2034,6 +2062,30 @@ declare class UnitOfWork<Evt extends AnyDomainEvent, TCtx, TRepos extends Record
2034
2062
  * ```
2035
2063
  */
2036
2064
  type QueryTypeMap = Record<string, unknown>;
2065
+ /**
2066
+ * Construction options for {@link QueryBus}.
2067
+ *
2068
+ * @template E - The error channel type of the bus.
2069
+ */
2070
+ interface QueryBusOptions<E = string> {
2071
+ /**
2072
+ * Maps a thrown value (a handler that throws, or dispatch to an
2073
+ * unregistered query type) into the bus's error channel `E`. Defaults to
2074
+ * {@link describeThrown}, which renders any thrown value as a `string`.
2075
+ * base-error's `toStructuredError` fits this slot directly when `E` is a
2076
+ * `StructuredError`.
2077
+ */
2078
+ errorMapper?: (thrown: unknown) => E;
2079
+ }
2080
+ /**
2081
+ * Constructor arguments for {@link QueryBus}. When `E` is the default `string`,
2082
+ * options are optional (the built-in {@link describeThrown} mapper applies).
2083
+ * When `E` is widened, an `errorMapper` is required, so a typed channel can
2084
+ * never silently fall back to string values.
2085
+ */
2086
+ type QueryBusArgs<E> = [E] extends [string] ? [options?: QueryBusOptions<E>] : [options: QueryBusOptions<E> & {
2087
+ errorMapper: (thrown: unknown) => E;
2088
+ }];
2037
2089
  /**
2038
2090
  * Query Bus interface for dispatching queries to their handlers.
2039
2091
  * Provides a centralized way to execute queries with handler registration.
@@ -2057,7 +2109,7 @@ type QueryTypeMap = Record<string, unknown>;
2057
2109
  * // result: Result<unknown, string>
2058
2110
  * ```
2059
2111
  */
2060
- interface IQueryBus<TMap extends QueryTypeMap = QueryTypeMap> {
2112
+ interface IQueryBus<TMap extends QueryTypeMap = QueryTypeMap, E = string> {
2061
2113
  /**
2062
2114
  * Executes a query by dispatching it to the registered handler.
2063
2115
  * When a type map is provided, the return type is inferred from the query type.
@@ -2067,8 +2119,8 @@ interface IQueryBus<TMap extends QueryTypeMap = QueryTypeMap> {
2067
2119
  */
2068
2120
  execute<Q extends Query & {
2069
2121
  type: keyof TMap & string;
2070
- }>(query: Q): Promise<Result<TMap[Q["type"]], string>>;
2071
- execute<Q extends Query, R>(query: Q): Promise<Result<R, string>>;
2122
+ }>(query: Q): Promise<Result<TMap[Q["type"]], E>>;
2123
+ execute<Q extends Query, R>(query: Q): Promise<Result<R, E>>;
2072
2124
  /**
2073
2125
  * Executes a query by dispatching it to the registered handler.
2074
2126
  * Throws an error if no handler is registered.
@@ -2135,8 +2187,10 @@ interface IQueryBus<TMap extends QueryTypeMap = QueryTypeMap> {
2135
2187
  * const result = await bus.execute({ type: "GetOrder", orderId: "123" });
2136
2188
  * ```
2137
2189
  */
2138
- declare class QueryBus<TMap extends QueryTypeMap = QueryTypeMap> implements IQueryBus<TMap> {
2190
+ declare class QueryBus<TMap extends QueryTypeMap = QueryTypeMap, E = string> implements IQueryBus<TMap, E> {
2139
2191
  private readonly handlers;
2192
+ private readonly errorMapper;
2193
+ constructor(...args: QueryBusArgs<E>);
2140
2194
  register<K extends keyof TMap & string, Q extends Query & {
2141
2195
  type: K;
2142
2196
  } = Query & {
@@ -2144,8 +2198,8 @@ declare class QueryBus<TMap extends QueryTypeMap = QueryTypeMap> implements IQue
2144
2198
  }>(queryType: K, handler: QueryHandler<Q, TMap[K]>): void;
2145
2199
  execute<Q extends Query & {
2146
2200
  type: keyof TMap & string;
2147
- }>(query: Q): Promise<Result<TMap[Q["type"]], string>>;
2148
- execute<Q extends Query, R>(query: Q): Promise<Result<R, string>>;
2201
+ }>(query: Q): Promise<Result<TMap[Q["type"]], E>>;
2202
+ execute<Q extends Query, R>(query: Q): Promise<Result<R, E>>;
2149
2203
  executeUnsafe<Q extends Query & {
2150
2204
  type: keyof TMap & string;
2151
2205
  }>(query: Q): Promise<TMap[Q["type"]]>;
@@ -2792,4 +2846,4 @@ declare abstract class ValueObject<T extends object> implements IValueObject<T>
2792
2846
  */
2793
2847
  declare function voValidated<T>(t: T, validate: (issues: ValidationError, value: T) => void, message?: string): Result<VO<T>, ValidationError>;
2794
2848
 
2795
- export { type AggregateClass, type AggregateConfig, AggregateRoot, AggregateSnapshot, AnyDomainEvent, type Command, CommandBus, type CommandHandler, CommitError, CreateDomainEventOptions, DeepEqualExceptOptions, DomainError, Entity, type EventBus, EventBusImpl, type EventHandler, EventSourcedAggregate, IAggregateRoot, type ICommandBus, type IEntity, IEventSourcedAggregate, type IQueryBus, type IQueryableRepository, type IRepository, type IUnitOfWorkRepository, type IValueObject, Id, type Identifiable, IdentityMap, InMemoryOutbox, InfrastructureError, NestedUnitOfWorkError, type OnceOptions, type Outbox, type OutboxRecord, type Query, QueryBus, type QueryHandler, type RepositoryFactories, type RetryPolicy, RetryingTransactionScope, RollbackError, type RunOptions, TransactionClosedError, type TransactionScope, type TransactionalOptions, UnitOfWork, type UnitOfWorkContext, type UnitOfWorkDeps, type UnitOfWorkSession, type VO, ValueObject, Version, computeBackoffDelay, deepFreeze, entityIds, findEntityById, freezeShallow, hasEntityId, removeEntityById, replaceEntityById, sameEntity, updateEntityById, vo, voEquals, voEqualsExcept, voValidated, voWithValidation, withCommit };
2849
+ export { type AggregateClass, type AggregateConfig, AggregateRoot, AggregateSnapshot, AnyDomainEvent, type Command, CommandBus, type CommandBusOptions, type CommandHandler, CommitError, CreateDomainEventOptions, DeepEqualExceptOptions, DomainError, Entity, type EventBus, EventBusImpl, type EventHandler, EventSourcedAggregate, IAggregateRoot, type ICommandBus, type IEntity, IEventSourcedAggregate, type IQueryBus, type IQueryableRepository, type IRepository, type IUnitOfWorkRepository, type IValueObject, Id, type Identifiable, IdentityMap, InMemoryOutbox, InfrastructureError, NestedUnitOfWorkError, type OnceOptions, type Outbox, type OutboxRecord, type Query, QueryBus, type QueryBusOptions, type QueryHandler, type RepositoryFactories, type RetryPolicy, RetryingTransactionScope, RollbackError, type RunOptions, TransactionClosedError, type TransactionScope, type TransactionalOptions, UnitOfWork, type UnitOfWorkContext, type UnitOfWorkDeps, type UnitOfWorkSession, type VO, ValueObject, Version, computeBackoffDelay, deepFreeze, entityIds, findEntityById, freezeShallow, hasEntityId, removeEntityById, replaceEntityById, sameEntity, updateEntityById, vo, voEquals, voEqualsExcept, voValidated, voWithValidation, withCommit };
package/dist/index.js CHANGED
@@ -711,13 +711,6 @@ function createDomainEvent(type, payload, options) {
711
711
  return deepFreeze(event);
712
712
  }
713
713
  __name(createDomainEvent, "createDomainEvent");
714
- function createDomainEventWithMetadata(type, payload, metadata, options) {
715
- return createDomainEvent(type, payload, {
716
- ...options,
717
- metadata
718
- });
719
- }
720
- __name(createDomainEventWithMetadata, "createDomainEventWithMetadata");
721
714
  function copyMetadata(sourceEvent, additionalMetadata) {
722
715
  return {
723
716
  ...sourceEvent.metadata ?? {},
@@ -1483,52 +1476,38 @@ var AggregateDeletedError = class extends BaseError {
1483
1476
  }
1484
1477
  };
1485
1478
  var AggregateNotFoundError = class extends InfrastructureError {
1486
- constructor(aggregateType, id, cause) {
1487
- super(`Aggregate not found: ${aggregateType}(${id})`, cause, {
1488
- name: "AggregateNotFoundError"
1489
- });
1490
- this.aggregateType = aggregateType;
1491
- this.id = id;
1492
- this.withUserMessage(
1493
- `The requested ${aggregateType} could not be found.`
1494
- );
1495
- }
1496
1479
  static {
1497
1480
  __name(this, "AggregateNotFoundError");
1498
1481
  }
1499
- };
1500
- var DuplicateAggregateError = class extends InfrastructureError {
1501
- constructor(aggregateType, aggregateId, cause) {
1482
+ aggregateType;
1483
+ id;
1484
+ constructor(options) {
1502
1485
  super(
1503
- `Duplicate aggregate: ${aggregateType}(${aggregateId}) already exists`,
1504
- cause,
1505
- { name: "DuplicateAggregateError" }
1506
- );
1507
- this.aggregateType = aggregateType;
1508
- this.aggregateId = aggregateId;
1509
- this.withUserMessage(
1510
- `This ${aggregateType} already exists. It may have been created by a concurrent request.`
1486
+ `Aggregate not found: ${options.aggregateType}(${options.id})`,
1487
+ options.cause,
1488
+ { name: "AggregateNotFoundError" }
1511
1489
  );
1490
+ this.aggregateType = options.aggregateType;
1491
+ this.id = options.id;
1512
1492
  }
1493
+ };
1494
+ var DuplicateAggregateError = class extends InfrastructureError {
1513
1495
  static {
1514
1496
  __name(this, "DuplicateAggregateError");
1515
1497
  }
1516
- };
1517
- var ConcurrencyConflictError = class extends InfrastructureError {
1518
- constructor(aggregateType, aggregateId, expectedVersion, actualVersion, cause) {
1498
+ aggregateType;
1499
+ aggregateId;
1500
+ constructor(options) {
1519
1501
  super(
1520
- `Concurrency conflict on ${aggregateType}(${aggregateId}): expected version ${expectedVersion}, actual ${actualVersion}`,
1521
- cause,
1522
- { name: "ConcurrencyConflictError" }
1523
- );
1524
- this.aggregateType = aggregateType;
1525
- this.aggregateId = aggregateId;
1526
- this.expectedVersion = expectedVersion;
1527
- this.actualVersion = actualVersion;
1528
- this.withUserMessage(
1529
- "This resource was updated by another request. Please reload and try again."
1502
+ `Duplicate aggregate: ${options.aggregateType}(${options.aggregateId}) already exists`,
1503
+ options.cause,
1504
+ { name: "DuplicateAggregateError" }
1530
1505
  );
1506
+ this.aggregateType = options.aggregateType;
1507
+ this.aggregateId = options.aggregateId;
1531
1508
  }
1509
+ };
1510
+ var ConcurrencyConflictError = class extends InfrastructureError {
1532
1511
  static {
1533
1512
  __name(this, "ConcurrencyConflictError");
1534
1513
  }
@@ -1538,6 +1517,21 @@ var ConcurrencyConflictError = class extends InfrastructureError {
1538
1517
  * the use case, and retry on this exception.
1539
1518
  */
1540
1519
  retryable = true;
1520
+ aggregateType;
1521
+ aggregateId;
1522
+ expectedVersion;
1523
+ actualVersion;
1524
+ constructor(options) {
1525
+ super(
1526
+ `Concurrency conflict on ${options.aggregateType}(${options.aggregateId}): expected version ${options.expectedVersion}, actual ${options.actualVersion}`,
1527
+ options.cause,
1528
+ { name: "ConcurrencyConflictError" }
1529
+ );
1530
+ this.aggregateType = options.aggregateType;
1531
+ this.aggregateId = options.aggregateId;
1532
+ this.expectedVersion = options.expectedVersion;
1533
+ this.actualVersion = options.actualVersion;
1534
+ }
1541
1535
  };
1542
1536
 
1543
1537
  // src/aggregate/event-sourced-aggregate.ts
@@ -1676,6 +1670,10 @@ var CommandBus = class {
1676
1670
  __name(this, "CommandBus");
1677
1671
  }
1678
1672
  handlers = /* @__PURE__ */ new Map();
1673
+ errorMapper;
1674
+ constructor(...args) {
1675
+ this.errorMapper = args[0]?.errorMapper ?? describeThrown;
1676
+ }
1679
1677
  register(commandType, handler) {
1680
1678
  if (this.handlers.has(commandType)) {
1681
1679
  throw new Error(
@@ -1687,12 +1685,16 @@ var CommandBus = class {
1687
1685
  async execute(command) {
1688
1686
  const handler = this.handlers.get(command.type);
1689
1687
  if (!handler) {
1690
- return err(`No handler registered for command type: ${command.type}`);
1688
+ return err(
1689
+ this.errorMapper(
1690
+ new Error(`No handler registered for command type: ${command.type}`)
1691
+ )
1692
+ );
1691
1693
  }
1692
1694
  try {
1693
1695
  return await handler(command);
1694
1696
  } catch (error) {
1695
- return err(describeThrown(error));
1697
+ return err(this.errorMapper(error));
1696
1698
  }
1697
1699
  }
1698
1700
  };
@@ -2165,6 +2167,10 @@ var QueryBus = class {
2165
2167
  __name(this, "QueryBus");
2166
2168
  }
2167
2169
  handlers = /* @__PURE__ */ new Map();
2170
+ errorMapper;
2171
+ constructor(...args) {
2172
+ this.errorMapper = args[0]?.errorMapper ?? describeThrown;
2173
+ }
2168
2174
  register(queryType, handler) {
2169
2175
  if (this.handlers.has(queryType)) {
2170
2176
  throw new Error(
@@ -2176,13 +2182,17 @@ var QueryBus = class {
2176
2182
  async execute(query) {
2177
2183
  const handler = this.handlers.get(query.type);
2178
2184
  if (!handler) {
2179
- return err(`No handler registered for query type: ${query.type}`);
2185
+ return err(
2186
+ this.errorMapper(
2187
+ new Error(`No handler registered for query type: ${query.type}`)
2188
+ )
2189
+ );
2180
2190
  }
2181
2191
  try {
2182
2192
  const result = await handler(query);
2183
2193
  return ok(result);
2184
2194
  } catch (error) {
2185
- return err(describeThrown(error));
2195
+ return err(this.errorMapper(error));
2186
2196
  }
2187
2197
  }
2188
2198
  async executeUnsafe(query) {
@@ -2428,6 +2438,6 @@ function voValidated(t, validate, message = "Validation failed") {
2428
2438
  }
2429
2439
  __name(voValidated, "voValidated");
2430
2440
 
2431
- export { AggregateDeletedError, AggregateNotFoundError, AggregateRoot, CommandBus, CommitError, ConcurrencyConflictError, DomainError, DuplicateAggregateError, Entity, EventBusImpl, EventHarvestError, EventSourcedAggregate, IdentityMap, InMemoryOutbox, InfrastructureError, MissingHandlerError, NestedUnitOfWorkError, QueryBus, RetryingTransactionScope, RollbackError, TransactionClosedError, UnenrolledChangesError, UnitOfWork, ValueObject, computeBackoffDelay, copyMetadata, createDomainEvent, createDomainEventWithMetadata, deepEqual, deepEqualExcept, deepFreeze, deepOmit, entityIds, findEntityById, freezeShallow, hasEntityId, mergeMetadata, removeEntityById, replaceEntityById, resetClockFactory, resetEventIdFactory, sameEntity, sameVersion, setClockFactory, setEventIdFactory, updateEntityById, vo, voEquals, voEqualsExcept, voValidated, voWithValidation, withClockFactory, withCommit, withEventIdFactory };
2441
+ export { AggregateDeletedError, AggregateNotFoundError, AggregateRoot, CommandBus, CommitError, ConcurrencyConflictError, DomainError, DuplicateAggregateError, Entity, EventBusImpl, EventHarvestError, EventSourcedAggregate, IdentityMap, InMemoryOutbox, InfrastructureError, MissingHandlerError, NestedUnitOfWorkError, QueryBus, RetryingTransactionScope, RollbackError, TransactionClosedError, UnenrolledChangesError, UnitOfWork, ValueObject, computeBackoffDelay, copyMetadata, createDomainEvent, deepEqual, deepEqualExcept, deepFreeze, deepOmit, entityIds, findEntityById, freezeShallow, hasEntityId, mergeMetadata, removeEntityById, replaceEntityById, resetClockFactory, resetEventIdFactory, sameEntity, sameVersion, setClockFactory, setEventIdFactory, updateEntityById, vo, voEquals, voEqualsExcept, voValidated, voWithValidation, withClockFactory, withCommit, withEventIdFactory };
2432
2442
  //# sourceMappingURL=index.js.map
2433
2443
  //# sourceMappingURL=index.js.map