# @ddd-ts-kit/validation 中文文档

规范地址：https://npmcn.com/package/%40ddd-ts-kit/validation/

版本：0\.106\.0
npm 元数据更新时间：2026-10-31T21:44:56.284Z
中文译文更新时间：2026-10-31T23:15:52.560Z
软件包许可：MIT

本文由上游 README 机器翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%40ddd-ts-kit/validation
原文对照：https://npmcn.com/package/%40ddd-ts-kit/validation/?lang=original
项目主页：https://gbourgeat.github.io/ddd-ts-kit/packages/validation/
源码仓库：https://github.com/gbourgeat/ddd-ts-kit

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：120 kB，152 个文件
- 模块格式：ES Module
- TypeScript 类型：包内自带
- Node.js 要求：\>=22\.12\.0
- 运行依赖：1 个
- peer 依赖：zod ^4\.0\.0，typebox ^1\.0\.0，class-validator \>=0\.14\.0，reflect-metadata \>=0\.2\.0，class-transformer \>=0\.5\.0，class-validator-jsonschema \>=5\.0\.0
- 首次发布：2026-10-11T09:42:12.680Z
- 最新版本发布：2026-10-31T21:44:55.954Z
- 已发布版本数：63（近 12 个月 63 个）

## 中文简介

一个用于输入验证的模式契约，带有用于zod、类验证器、TypeBox和标准模式的适配器。

## 安装

```sh
npm install @ddd-ts-kit/validation
```

也可以使用：`pnpm add @ddd-ts-kit/validation`、`yarn add @ddd-ts-kit/validation`、`bun add @ddd-ts-kit/validation`

## README

**英语** · [法语](https://npmcn.com/package/%40ddd-ts-kit/validation/README.fr.md)

# @ddd-ts-kit /验证

一个 `Schema` 在边界处进行输入验证的契约，带有用于zod的适配器， 类验证器、TypeBox和任何标准模式库。路由，dto和OpenAPI生成器依赖 在契约上，这样验证库可以在不触及它们的情况下进行更改。

业务规则不属于这里：它们存在于域中，从不导入验证库。

![A request at the boundary: its params, query, headers and body each go through the Schema the route declares; when every part parses, the handler receives typed values and the domain checks the business rules; when a part fails, its issues are collected with the name of the part in front of their path, such as body.items.0.sku, and the request is refused with one ValidationError, code VALIDATION\_FAILED, answered 422 with the list of issues; the same schemas give the JSON Schema of the OpenAPI document](https://raw.githubusercontent.com/gbourgeat/ddd-ts-kit/main/docs/assets/diagrams/validation-package-at-the-boundary.svg)

模式检查 **形状** 输入的内容：类型、必填字段、格式、长度。什么 业务决定（过去的插槽，库存以上的数量）由域检查， 无论进入点是什么：

```ts
import { z } from 'zod';
import { DomainRefusal } from '@ddd-ts-kit/kernel';
import { zodSchema } from '@ddd-ts-kit/validation/zod';

// Instead of: a business rule hidden in the schema of one route, which a job or a consumer never meets.
export const bookWithARule = zodSchema(
  z.object({ startsAt: z.iso.datetime() }).refine((body) => Date.parse(body.startsAt) > Date.now(), 'The slot is in the past.'),
);

// Write: the shape in the schema...
export const book = zodSchema(z.object({ startsAt: z.iso.datetime() }));

// ...and the rule in the domain, with its own refusal code.
export const SchedulingCodes = { SlotInThePast: 'SLOT_IN_THE_PAST' } as const;

export class SlotInThePast extends DomainRefusal {
  readonly code = SchedulingCodes.SlotInThePast;

  static atStart(startsAt: Date) {
    return SlotInThePast.refusal(`The slot at ${startsAt.toISOString()} is in the past.`, {
      startsAt: startsAt.toISOString(),
    });
  }
}

export function startingAt(startsAt: Date, now: Date): Date {
  if (startsAt <= now) {
    throw SlotInThePast.atStart(startsAt);
  }

  return startsAt;
}
```

## 合同

```ts
import { parseOrThrow, type Infer, type Schema } from '@ddd-ts-kit/validation';

interface Schema<T> {
  parse(input: unknown): Result<T, ValidationIssue[]>;
  toJsonSchema(): JsonSchema;
}

type ValidationIssue = { path: string; code: string; message: string }; // path: "items.0.sku"
```

模式是一个值：每个输入一个值，传递给使用它进行验证的路由或事件。 它从未绑定到容器中，所以它是一个接口，而不是一个可注入端口（抽象） 类)：在组合根中没有什么需要声明的。

-   `parse` 永远不会为无效输入抛出异常：它会返回 `err(issues)`.
-   `parseOrThrow(schema, input)` 返回值或抛出 `ValidationError`,一个 `DomainError` 使用代码 `VALIDATION_FAILED` 和 `issues`.
-   `Infer<typeof schema>` 给出解析后的类型。
-   `customSchema(parse, jsonSchema?)` 从普通函数构建模式。
-   `sharedSchema(name, schema)` 记录一个模式一次，在 `name` (见 [共享模式](https://npmcn.com/package/%40ddd-ts-kit/validation/#readme-shared-schemas)）。

```ts
import { err, ok } from '@ddd-ts-kit/kernel';
import { ValidationError, customSchema, parseOrThrow, type Infer } from '@ddd-ts-kit/validation';

// A schema from a plain function: a check written by hand, or a double in a test.
const quantity = customSchema(
  (input: unknown) =>
    typeof input === 'number' && Number.isInteger(input) && input > 0
      ? ok(input)
      : err([{ path: '', code: 'not_a_quantity', message: 'A positive whole number is expected.' }]),
  { type: 'integer', minimum: 1 },
);

type Quantity = Infer<typeof quantity>; // number

export const refused = quantity.parse('3'); // { ok: false, error: [{ path: '', code: 'not_a_quantity', ... }] }
export const three: Quantity = parseOrThrow(quantity, 3); // 3

try {
  parseOrThrow(quantity, 0);
} catch (error) {
  if (error instanceof ValidationError) {
    console.log(error.code, error.issues); // VALIDATION_FAILED [{ path: '', code: 'not_a_quantity', ... }]
  }
}
```

根条目不导入第三方包。每个适配器都存在于它自己的子路径和它的 库是可选的对等依赖项：只安装您使用的库。

## 在路线上

的路线 `@ddd-ts-kit/http` 为它所读取的请求的每个部分命名一个模式。每一个部分 在处理程序运行之前解析，所有部分的问题一起得到回答，并且每个 Issue path从它的部分开始：

```ts
import { z } from 'zod';
import { defineRoute, executeRoute, type RawRequest } from '@ddd-ts-kit/http';
import { zodSchema } from '@ddd-ts-kit/validation/zod';

const placeOrder = defineRoute(
  {
    method: 'POST',
    path: '/orders',
    operationId: 'placeOrder',
    summary: 'Place an order',
    request: {
      body: zodSchema(z.object({ items: z.array(z.object({ sku: z.string().min(1), quantity: z.number().int().positive() })).min(1) })),
    },
    responses: { 201: { description: 'The order is placed.', body: zodSchema(z.object({ lines: z.number() }), { io: 'output' }) } },
    security: false,
  },
  async ({ body }) => ({ status: 201, body: { lines: body.items.length } }), // body is typed: { items: { sku, quantity }[] }
);

const request = (body: unknown): RawRequest => ({ params: {}, query: {}, headers: {}, body, raw: undefined });

export const placed = await executeRoute(placeOrder, request({ items: [{ sku: 'A-1', quantity: 2 }] })); // 201, { lines: 1 }
export const refused = await executeRoute(placeOrder, request({ items: [{ sku: 'A-1', quantity: 0 }] }));
// 422, a problem with the code VALIDATION_FAILED and
// issues: [{ path: 'body.items.0.quantity', code: 'too_small', message: 'Too small: expected number to be >0' }]
```

处理程序永远不会看到无效请求，路由的OpenAPI文档描述了 同一个身体，来自 `toJsonSchema()`.

## 萨德

```sh
npm i zod
```

```ts
import { z } from 'zod';
import { zodSchema } from '@ddd-ts-kit/validation/zod';

declare const input: unknown; // what the request carried

const placeOrder = zodSchema(
  z.object({
    customerId: z.string().uuid(),
    items: z.array(z.object({ sku: z.string().min(1), quantity: z.number().int().positive() })).min(1),
  }),
);

const result = placeOrder.parse(input);
if (!result.ok) {
  console.log(result.error); // [{ path: 'items.0.sku', code: 'too_small', message: '...' }]
}
```

JSON模式来自于zod4 `z.toJSONSchema` (草案2020-12，可在OpenAPI中使用 3.1)。默认情况下，它描述输入端；通过 `{ io: 'output' }` 用于响应体。一个 类型JSON模式不能表示(a) `Date`,一个 `bigint`)写为 `{}` 而不是 辜负了这一代人。

## class-validator

```sh
npm i class-validator class-transformer class-validator-jsonschema reflect-metadata
```

```ts
import { Type } from 'class-transformer';
import { ArrayMinSize, IsInt, IsString, IsUUID, Min, ValidateNested } from 'class-validator';
import { classSchema } from '@ddd-ts-kit/validation/class-validator';

class OrderItemDto {
  @IsString() declare sku: string;
  @IsInt() @Min(1) declare quantity: number;
}

class PlaceOrderDto {
  @IsUUID() declare customerId: string;

  @ValidateNested({ each: true })
  @Type(() => OrderItemDto)
  @ArrayMinSize(1)
  declare items: OrderItemDto[];
}

export const placeOrder = classSchema(PlaceOrderDto);
```

`parse` 返回类的实例。 `whitelist` 和 `forbidNonWhitelisted` 都在附近 默认值：未声明的属性是一个问题。覆盖与 `classSchema(Dto, { validator: { forbidNonWhitelisted: false }, transform: { ... } })`. 嵌套错误被扁平化(`items.0.quantity`)，每个失败的约束有一个问题，即 约束名称为 `code`． JSON模式是2020-12年的草案，包含嵌套类 可从下面的类访问 `$defs`.

需要 `experimentalDecorators` 在项目的 `tsconfig.json`.

每个字段都是 `declare`D，没有初值。类转换器保留a的初始值 输入缺少的字段： `customerId = ''` 下 `@IsString()` 在没有顾客的情况下会接受订单吗 Pass，好像它的名字是空的一样。可选字段是 `@IsOptional()` 与 `declare note?: string;`. `classSchema` 拒绝其新实例持有带有 `DeclarationError` 命名它的字段和修复。

声明一次的DTO类在整个(`classSchema(SlotDto, { shared: true })`，或嵌套 与 `@ValidateNested()` 和 `@Type`, `@IsDefined()` 当需要时：嵌套验证 允许丢失的对象通过)，扩展(`class BookBody extends SlotDto { ... }`)或in 部分是通过复制约束、嵌套类型和转换的三个帮助程序完成的 它们所保留的属性：

```ts
import { IsEmail, IsIn, IsISO8601, IsString, IsUUID } from 'class-validator';
import { omitClass, partialClass, pickClass } from '@ddd-ts-kit/validation/class-validator';

class AppointmentDto {
  @IsUUID() declare patientId: string;
  @IsISO8601() declare startsAt: string;
  @IsISO8601() declare endsAt: string;
  @IsIn(['booked', 'cancelled']) declare status: 'booked' | 'cancelled';
}

class PatientDto {
  @IsString() declare name: string;
  @IsEmail() declare email: string;
}

export class RescheduleBody extends pickClass(AppointmentDto, ['startsAt', 'endsAt']) {}
export class BookBody extends omitClass(AppointmentDto, ['status']) {}
export class EditPatientBody extends partialClass(PatientDto) {} // every property optional, checked when present
```

扩展帮助器的类在契约中命名形状。选择一个携带的属性 没有约束被拒绝：验证将把它作为未知剥离。中嵌套的派生类 另一个通过 `@Type` 声明至少一个自己的属性：class-validator-jsonschema 只从类自己的装饰器记录类。

## TypeBox

```sh
npm i typebox
```

```ts
import Type from 'typebox';
import { typeboxSchema } from '@ddd-ts-kit/validation/typebox';

export const slot = Type.Object({ startsAt: Type.String({ format: 'date-time' }), minutes: Type.Integer({ default: 30 }) }, { $id: 'Slot' });
export const book = typeboxSchema(Type.Object({ patientId: Type.String({ minLength: 1 }), slot }));
export const page = typeboxSchema(Type.Object({ limit: Type.Integer() }), { coerce: true }); // query strings
```

架构编译一次(`typebox/compile`）。 `parse` 填充默认值并删除 模式没有声明的属性（如zod），然后检查；问题代码是JSON 失败的架构关键字(`required`， `format`， `minLength`……)。 `{ coerce: true }` 转换 首先将文本转换为声明的类型，用于路径参数和查询字符串。TypeBox模式是 JSON模式已经存在： `toJsonSchema()` 对象构建的嵌套模式 `$id` 移动 下 `$defs` 并且被引用，所以它会在合同中出现；具有自己的模式 `$id` 是 共享。在其路径上报告丢失的属性(`slot.startsAt`)，正如zod所报道的那样 两个不同的模式在一个模式下 `$id` 是拒绝了。 `Type.Pick`， `Type.Omit` 和 `Type.Partial` 重用模式的一部分，作为一个新的形状； `Type.Intersect([slot, Type.Object({...})])` 扩展了它 并将共享部分作为参考(`allOf: [{ $ref: Slot }, ...]`),而 `Type.Interface([slot], {...})` 写入一个新的平面形状。（`Type.Composite` 属于 `@sinclair/typebox` 0.34， TypeBox 1中不存在。) 为…而写 `typebox` 的后续版本 `@sinclair/typebox`）。

## 标准模式（validbot, arktype，…）

```ts
import * as v from 'valibot';
import { standardSchema } from '@ddd-ts-kit/validation/standard-schema';

export const placeOrder = standardSchema(
  v.object({ customerId: v.pipe(v.string(), v.uuid()) }),
  // Optional: the JSON Schema of the contract, which this adapter does not derive.
  { type: 'object', required: ['customerId'], properties: { customerId: { type: 'string', format: 'uuid' } } },
);
```

适配器验证 `~standard.validate` 标准模式v1。的JSON模式 合同是第二个参数，当它是手写的，或由出口商 你的图书馆);否则就是库在实现同伴时自己转换的对象 标准JSON模式v1 (`~standard.jsonSchema`;通过zod 4验证，而arktype 并且有效通过 `toStandardJsonSchema` 的 `@valibot/to-json-schema` 宣布它，但不是 输入端默认为和 `standardSchema(schema, undefined, { io: 'output' })` 得到回应；否则 `{}`， OpenAPI文档将其显示为“任意值”。图式 库不能以JSON模式编写（zod拒绝） `z.date()`， `z.coerce.date()`， `z.custom`)是 记录 `{}` 也：传递它的JSON模式作为第二个参数。该规范没有错误代码，所以每个问题 有代码吗？ `invalid`． 异步验证的模式 `parse` 在边界处不支持Throw: async模式。

## 共享模式

多个路由使用的模式声明一次，记录一次：的OpenAPI生成器 `@ddd-ts-kit/http` 写在里面 `components.schemas` 然后用 `$ref` 从每一个 路线，整个或嵌套。当模式的JSON模式是对它自己的JSON模式的引用时，模式是共享的 定义(`{ $ref: '#/$defs/Slot', $defs: { Slot: ... } }`)，每个库达到它的形式 自己的方式:

![A schema shared under the name Slot: its JSON Schema is a reference to its own definition; the routes reschedule and book use it, one whole and one nested in its body; the OpenAPI generator writes Slot once under components.schemas and points at it with $ref from both routes, instead of two copies](https://raw.githubusercontent.com/gbourgeat/ddd-ts-kit/main/docs/assets/diagrams/validation-package-shared-schemas.svg)

```ts
import Type from 'typebox';
import * as v from 'valibot';
import { z } from 'zod';
import { sharedSchema } from '@ddd-ts-kit/validation';
import { classSchema } from '@ddd-ts-kit/validation/class-validator';
import { standardSchema } from '@ddd-ts-kit/validation/standard-schema';
import { typeboxSchema } from '@ddd-ts-kit/validation/typebox';
import { zodSchema } from '@ddd-ts-kit/validation/zod';

declare class PatientDto {
  name: string;
  email: string;
}

// zod: an id in its metadata, kept when the schema is nested in another one
const slot = z.object({ startsAt: z.iso.datetime(), endsAt: z.iso.datetime() }).meta({ id: 'Slot' });
export const reschedule = zodSchema(slot);
export const book = zodSchema(z.object({ patientId: z.uuid(), slot }));

// class-validator: the name of the class
export const patient = classSchema(PatientDto, { shared: true });

// TypeBox: an $id in its options, kept when the schema is nested in another one
const money = Type.Object({ cents: Type.Integer(), currency: Type.String() }, { $id: 'Money' });
export const price = typeboxSchema(Type.Object({ amount: money }));

// any schema, the Standard Schema ones included
const amountSchema = v.object({ cents: v.pipe(v.number(), v.integer()) });
export const amount = sharedSchema('Amount', standardSchema(amountSchema));
```

`sharedNameOf(jsonSchema)` 回答JSON模式共享的名称，如果有的话：

```ts
import { customSchema, sharedNameOf, sharedSchema } from '@ddd-ts-kit/validation';

const amount = sharedSchema('Amount', customSchema((input) => ({ ok: true, value: input }), { type: 'integer' }));

export const jsonSchema = amount.toJsonSchema(); // { $ref: '#/$defs/Amount', $defs: { Amount: { type: 'integer' } } }
export const name = sharedNameOf(jsonSchema); // 'Amount'
```

`schemaBehind.of(schema)` 回答模式 `sharedSchema` 是给定的，那么另一个又是什么呢 附加到它的包（字段） `withFieldAccess` 的 `@ddd-ts-kit/http` 储备)用于 也是共享的。

表示层的共享模式位于 `generate schema` 写在下面 `presentation/http/schemas/` 上下文的，上下文下的 `presentation/` 共享内核的 有几种情况可以回答这个问题；从来没有在这个领域。

## 常见问题

### @ddd-ts-kit/validation 是什么？

一个用于输入验证的模式契约，带有用于zod、类验证器、TypeBox和标准模式的适配器。

### 如何安装 @ddd-ts-kit/validation？

在项目目录运行 npm install @ddd-ts-kit/validation，或使用 pnpm add / yarn add / bun add。

### @ddd-ts-kit/validation 支持 TypeScript 吗？

支持。包内自带 TypeScript 类型声明（package\.json 声明了 types 入口），安装后即可获得类型提示，无需再安装 @types 包。

### @ddd-ts-kit/validation 支持 ESM 还是 CommonJS？

以 ES Module 形式发布，推荐使用 import 引入。在 CommonJS 项目中，Node\.js 20\.19\+ / 22\.12\+ 可以直接 require\(\) 不含顶层 await 的 ES Module，更早的版本需要改用动态 import\(\)。

### @ddd-ts-kit/validation 需要什么版本的 Node\.js？

package\.json 的 engines 字段要求 Node\.js \>=22\.12\.0。版本不符时 npm 默认只给出警告，开启 engine-strict 后会拒绝安装。

### @ddd-ts-kit/validation 可以商用吗？

@ddd-ts-kit/validation 使用 MIT 许可。宽松许可：允许商业使用、修改和再分发，通常只需在分发时保留原版权与许可声明。以上为概括，不构成法律意见，请以许可原文为准。

### @ddd-ts-kit/validation 还在维护吗？

最新版本 0\.106\.0 发布于 2026/11/01（2 天前）；至今共发布 63 个版本，其中过去 12 个月发布了 63 个；首次发布于 2026/10/11。发布频率只是参考信号：功能稳定的工具可能很少需要更新，建议结合源码仓库的 issue 和提交记录判断。

