@shirudo/ddd-kit 1.0.0-rc.8 → 1.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.
package/README.md CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  Composable TypeScript toolkit for tactical Domain-Driven Design.
4
4
 
5
- > **Release Candidate**
5
+ > **Stable — 1.0**
6
6
  >
7
- > This library is in Release Candidate phase. The API is considered stable and ready for production evaluation. Please report any issues before the final 1.0.0 release.
7
+ > The public API is stable and follows [Semantic Versioning](https://semver.org/). Breaking changes bump the major and ship with a migration path in the [CHANGELOG](CHANGELOG.md).
8
8
 
9
9
  ## Badges
10
10
 
@@ -62,7 +62,7 @@ const email = createEmail("user@example.com");
62
62
 
63
63
  ### Value Objects
64
64
 
65
- Value Objects are immutable objects that are defined by their attributes rather than identity. They ensure data integrity by preventing modification after creation. Use the `vo()` helper function to create deeply frozen value objects that cannot be mutated, even nested objects and arrays. The library provides `voEquals()` for value-based equality comparison, `voEqualsExcept()` for comparing while ignoring specified keys (useful for metadata), and `voWithValidation()` for creating validated value objects at the App-Service boundary (returns Result). For Domain construction, prefer the `ValueObject` base class — its constructor throws on invariant violation via the `validate()` hook.
65
+ Value Objects are immutable objects that are defined by their attributes rather than identity. They ensure data integrity by preventing modification after creation. Use the `vo()` helper function to create deeply frozen value objects that cannot be mutated, even nested objects and arrays. The library provides `voEquals()` for value-based equality comparison, `voEqualsExcept()` for comparing while ignoring specified keys (useful for metadata), `voWithValidation()` for creating validated value objects at the App-Service boundary (returns Result), and `voValidated()` when you need to collect *every* invalid field into one `ValidationError` instead of failing on the first. For Domain construction, prefer the `ValueObject` base class — its constructor throws on invariant violation via the `validate()` hook.
66
66
 
67
67
  ### Entities
68
68
 
@@ -136,7 +136,7 @@ The `Result<T, E>` type provides functional error handling without exceptions. I
136
136
  ### Creating a Value Object
137
137
 
138
138
  ```typescript
139
- import { vo, voEquals, voEqualsExcept, voWithValidation, type VO } from "@shirudo/ddd-kit";
139
+ import { vo, voEquals, voEqualsExcept, voWithValidation, voValidated, type VO } from "@shirudo/ddd-kit";
140
140
 
141
141
  // Simple value object (Functional Style)
142
142
  type Money = VO<{
@@ -181,6 +181,20 @@ if (result.isOk()) {
181
181
  // throws via the `validate()` hook, so Domain code keeps a throw-based contract.
182
182
  // Reserve `voWithValidation` for parsing untrusted input at the App boundary.
183
183
 
184
+ // To report every invalid field at once, use `voValidated` — it collects all
185
+ // violations into one `ValidationError` (a Result-axis value, not a throw):
186
+ const parsed = voValidated(
187
+ { amount: -1, currency: "US" },
188
+ (issues, m) => {
189
+ if (m.amount < 0)
190
+ issues.addIssue({ message: "must be non-negative", path: ["amount"] });
191
+ if (m.currency.length !== 3)
192
+ issues.addIssue({ message: "must be a 3-letter code", path: ["currency"] });
193
+ }
194
+ );
195
+ // On failure: parsed.error.publicIssues() lists every violation.
196
+ // Render RFC 9457 with `toProblemDetails` from `@shirudo/ddd-kit/http`.
197
+
184
198
  // Value object with nested structures (deep freeze)
185
199
  const address = vo({
186
200
  street: "Main St",
@@ -1128,13 +1142,15 @@ For composition utilities (`map`, `flatMap`, `mapErr`, `match`, `unwrapOr`, `pip
1128
1142
  - **Infrastructure boundary returns `Result`** where corruption is an expected recoverable failure: `EventSourcedAggregate.loadFromHistory()`, `restoreFromSnapshotWithEvents()`.
1129
1143
  - **App-Service boundary returns `Result`**: `CommandBus.execute()`, `QueryBus.execute()`, `CommandHandler<C,R>`, `QueryHandler<Q,R>`, `withCommit()`. This is where you map errors to HTTP statuses, logs, etc.
1130
1144
  - **`voWithValidation`** is the explicit Result variant for parsing untrusted input at the App boundary. For Domain construction, use the `ValueObject` base class (constructor throws via `validate()`).
1145
+ - **`voValidated`** collects multiple field violations into one `ValidationError` (a Result-axis value you destructure, never catch). The opt-in `@shirudo/ddd-kit/http` entry point renders it as RFC 9457 via `toProblemDetails`.
1131
1146
 
1132
1147
  ## API Documentation
1133
1148
 
1134
1149
  This package is written in TypeScript and provides full type definitions. All types and functions are exported from the main entry point. You can explore the available APIs through your IDE's autocomplete or by examining the type definitions in `node_modules/@shirudo/ddd-kit/dist/index.d.ts`.
1135
1150
 
1136
1151
  Key exports include:
1137
- - `vo()`, `voEquals()`, `voEqualsExcept()`, `voWithValidation()` - Value Object utilities (`voWithValidation` is for the App-Service boundary; Domain construction goes through the `ValueObject` base class which throws via `validate()`)
1152
+ - `vo()`, `voEquals()`, `voEqualsExcept()`, `voWithValidation()`, `voValidated()` - Value Object utilities (`voWithValidation` / `voValidated` are for the App-Service boundary — the latter collects every field violation into one `ValidationError`; Domain construction goes through the `ValueObject` base class which throws via `validate()`)
1153
+ - `toProblemDetails()` from `@shirudo/ddd-kit/http` - renders a `ValidationError` as an RFC 9457 Problem Details object (opt-in subpath; keeps transport out of the core)
1138
1154
  - `IAggregateRoot<TId>` - Marker interface for Aggregate Root Entities
1139
1155
  - `AggregateRoot<TState, TId, TEvent?>` - Base class for creating Aggregate Root Entities without Event Sourcing (extends `Entity`, implements `IAggregateRoot<TId, TEvent>`). Optional `TEvent` parameter enables type-safe domain events
1140
1156
  - `EventSourcedAggregate<TState, TEvent, TId>` - Base class for Event-Sourced Aggregate Roots (extends `Entity`, implements `IEventSourcedAggregate<TId, TEvent>`)
package/dist/http.d.ts ADDED
@@ -0,0 +1,46 @@
1
+ import { ValidationError, ProblemDetailsOptions, ProblemDetails } from '@shirudo/base-error';
2
+
3
+ /** Extension member that carries the collected field issues. */
4
+ type ValidationProblemMember = "errors" | "invalid-params";
5
+ /**
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.
9
+ */
10
+ interface ValidationProblemOptions extends Omit<ProblemDetailsOptions, "extensions"> {
11
+ /**
12
+ * Extension member that carries the field issues. Default `"errors"`
13
+ * (`{ message, path, code?, pointer? }` entries). RFC 9457 does not
14
+ * standardize a multi-error member; `errors` is the common convention.
15
+ */
16
+ member?: ValidationProblemMember;
17
+ /** Extra public extension members merged alongside the issues. */
18
+ extensions?: Record<string, unknown>;
19
+ }
20
+ /**
21
+ * Projects a base-error {@link ValidationError} to an RFC 9457 Problem Details
22
+ * object with the collected field issues attached under an extension member.
23
+ *
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
27
+ * applies sensible validation defaults (`422`, `"Validation Failed"`), so the
28
+ * common boundary case is a one-liner instead of a footgun.
29
+ *
30
+ * This is a presentation/transport concern and ships from the opt-in
31
+ * `@shirudo/ddd-kit/http` entry point — the core kit stays transport-free.
32
+ *
33
+ * @example
34
+ * ```ts
35
+ * import { toProblemDetails } from "@shirudo/ddd-kit/http";
36
+ *
37
+ * if (result.isErr()) {
38
+ * return Response.json(toProblemDetails(result.error), { status: 422 });
39
+ * }
40
+ * // → { type, title: "Validation Failed", status: 422,
41
+ * // errors: [{ message: "must be a valid email", path: ["email"], pointer: "email" }] }
42
+ * ```
43
+ */
44
+ declare function toProblemDetails(error: ValidationError, options?: ValidationProblemOptions): ProblemDetails;
45
+
46
+ export { type ValidationProblemMember, type ValidationProblemOptions, toProblemDetails };
package/dist/http.js ADDED
@@ -0,0 +1,18 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+
4
+ // src/http/problem-details.ts
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
+ });
13
+ }
14
+ __name(toProblemDetails, "toProblemDetails");
15
+
16
+ export { toProblemDetails };
17
+ //# sourceMappingURL=http.js.map
18
+ //# sourceMappingURL=http.js.map
@@ -0,0 +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"]}