gwy-exam/deliverables/architecture/decisions/ADR-004-ts-strict-zod.md
2026-08-26 16:20:55 +08:00

47 lines
3.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ADR-004: 全 TypeScript strict + zod 运行时校验,全程禁止 any
## Status: Accepted (2026-08-26)
## Background
用户硬性要求:全 TypeScript**全程禁止 any**(评审铁律)。同时本项目数据来自**本地 JSON 文件**——文件是可变的外部输入可能被手工编辑、被旧版本程序写过、Docker 挂载来源不可控),若不运行时校验,读入的数据无法保证符合领域类型。
## Decision
1. **tsconfig 全开严格选项**`strict` 全家桶):
`strict / noImplicitAny / strictNullChecks / strictFunctionTypes / exactOptionalPropertyTypes / useUnknownInCatchVariables / noUncheckedIndexedAccess / noImplicitReturns / noUnusedLocals...`
2. **类型定义集中 + 单一契约源**:领域类型由 `packages/shared/src/schemas/*.schema.ts` 的 zod schema 经 `z.infer` 产出(不手写 interface前后端共用统一响应 `ApiResponse<T> = ApiSuccess<T> | ApiFailure`泛型0 any
3. **错误用联合类型**`AppError` 带错误码联合 + message`catch` 变量为 `unknown`,用类型守卫窄化,**不 any 强转**。
4. **zod 运行时校验获得类型收窄**:数据来自 JSON 文件,用 `zod` schema `parse(raw)`(返回类型即 T天然收窄。这是「从文件读数据 + 无 any」的最优实践。`z.infer` 产出具名类型。
5. **OpenAPI 契约同源**openapi.yaml 由 `zod-openapi` 从同一批 zod schema 自动反推生成,前端据此生成 TS 类型,无需另维护一份手写契约。
### zod 示例(纯 TS无 any
```ts
import { z } from 'zod';
const ModuleKeySchema = z.enum(['xingce-shuli', 'xingce-panduan', /** ... */]);
export type ModuleKey = z.infer<typeof ModuleKeySchema>;
const QuestionSchema = z.object({
id: z.string().uuid(),
subject: z.enum(['xingce', 'shenlun']),
module: ModuleKeySchema,
type: z.enum(['single', 'multiple', 'judge', 'blank', 'essay']),
stem: z.string().min(1),
options: z.array(z.string()).optional(),
answer: z.union([z.string(), z.array(z.string())]),
// ... 全部显式,无 any
}).openapi('Question');
export type Question = z.infer<typeof QuestionSchema>;
```
## Consequences
- 正面:`any` 从类型系统层面被禁止zod 让「不可信的 JSON 输入」在数据层就被收窄为强类型实体,后续代码零断言;前后端共享同一 schema契约一致领域类型、OpenAPI 契约、请求校验三者同源于 zod schema改一处全链跟随杜绝漂移。
- 负面:引入 zod + zod-openapi 依赖strict 选项与单一契约源会放大生成代码的报错面(但这是隔离性投资,越快暴露越好,避免把 `unknown` 漏到业务层)。
- 权衡:以 zod 作为**唯一**类型与契约来源,不另维护手写 interface 或手写 openapi.yaml消除「type 与 schema 两处对不上」的隐患。
## Related ADRs
- ADR-003JSON 数据层——zod 是其关键保障)
- ADR-006垂直切片 + 单一契约源——zod schema 驱动类型与契约)
- ADR-001 / ADR-002前后端均全 TS