47 lines
3.0 KiB
Markdown
47 lines
3.0 KiB
Markdown
# 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-003(JSON 数据层——zod 是其关键保障)
|
||
- ADR-006(垂直切片 + 单一契约源——zod schema 驱动类型与契约)
|
||
- ADR-001 / ADR-002(前后端均全 TS)
|