| 1 | --- |
| 2 | id: aitoearn-dto-vo-pattern |
| 3 | trigger: "when creating DTOs or VOs" |
| 4 | confidence: 0.95 |
| 5 | domain: typescript |
| 6 | source: local-repo-analysis |
| 7 | --- |
| 8 | |
| 9 | # Zod Schema + DTO/VO Pattern |
| 10 | |
| 11 | ## Action |
| 12 | Always define Zod schema first, then generate DTO/VO class using `createZodDto`. |
| 13 | |
| 14 | ## DTO (Input) |
| 15 | ```typescript |
| 16 | import { createZodDto, PaginationDtoSchema } from '@yikart/common' |
| 17 | import { z } from 'zod' |
| 18 | |
| 19 | export const CreateOrderDtoSchema = z.object({ |
| 20 | productId: z.string(), |
| 21 | quantity: z.number().int().positive().default(1), |
| 22 | returnTo: z.url().optional(), |
| 23 | }) |
| 24 | export class CreateOrderDto extends createZodDto(CreateOrderDtoSchema, 'CreateOrderDto') {} |
| 25 | ``` |
| 26 | |
| 27 | ## VO (Output) |
| 28 | ```typescript |
| 29 | import { createPaginationVo, createZodDto } from '@yikart/common' |
| 30 | import { z } from 'zod' |
| 31 | |
| 32 | export const OrderDetailVoSchema = z.object({ |
| 33 | id: z.string(), |
| 34 | amount: z.number(), |
| 35 | createdAt: z.coerce.date() |
| 36 | }) |
| 37 | export class OrderDetailVo extends createZodDto(OrderDetailVoSchema, 'OrderDetailVo') {} |
| 38 | export class OrderListVo extends createPaginationVo(OrderDetailVoSchema, 'OrderListVo') {} |
| 39 | ``` |
| 40 | |
| 41 | ## Controller Usage |
| 42 | ```typescript |
| 43 | // Regular VO - use static create() |
| 44 | return OrderDetailVo.create(data) |
| 45 | |
| 46 | // Pagination VO - use new |
| 47 | return new OrderListVo({ page, pageSize, total, totalPages, list }) |
| 48 | ``` |
| 49 | |
| 50 | ## Rules |
| 51 | - Never use entity as input (use DTO) |
| 52 | - Never return entity directly (use VO) |
| 53 | - Pagination input: use `PaginationDtoSchema` |
| 54 | - Pagination output fields: page, pageSize, totalPages, total, list |
| 55 | |
| 56 | ## Evidence |
| 57 | - Pattern defined in CLAUDE.md |
| 58 | - Used consistently across all services |
| 59 |