@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 +21 -5
- package/dist/http.d.ts +46 -0
- package/dist/http.js +18 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +516 -605
- package/dist/index.js +160 -263
- package/dist/index.js.map +1 -1
- package/dist/utils.d.ts +28 -28
- package/package.json +8 -3
package/README.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
Composable TypeScript toolkit for tactical Domain-Driven Design.
|
|
4
4
|
|
|
5
|
-
> **
|
|
5
|
+
> **Stable — 1.0**
|
|
6
6
|
>
|
|
7
|
-
>
|
|
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),
|
|
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`
|
|
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
|
package/dist/http.js.map
ADDED
|
@@ -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"]}
|