这套规范用于说明如何在代码库中设计和编写 TypeScript,尤其面向智能体:新增模式、库、适配器或抽象前先查看现有代码,但所有新增与重构行为都按本规范执行;仅在既有约定与规范兼容时沿用。
用途与范围:规范负责类型、schema、模块设计、注释和通用测试。前端约定(FSD、React、查询与变更、UI)归 tanstack-frontend;应用边界、传输契约、重试与持久化归 cloudflare-backend。显式任务指令与全局工作流约束仍然适用。
核心原则:默认使用带类型的抛出错误和被拒绝的 Promise,保留既有 Result/Effect 契约;解析而非校验,尽早靠近组合或应用根解析,不要只校验后丢弃信息;尽量让非法状态不可表示;优先构造即正确的 API;当品牌/精化/领域类型能防止真实错误(如混用标识符或单位、绕过解析、构造非法值)时使用它们;优先组合而非继承;优先命令式外壳/函数式内核;设计深而内聚、调用方负担低的模块;通过真实接缝测试行为,避免模块 mock 与 spy 驱动测试。
复用与操作契约:编写通用逻辑前先确认运行时、框架、SDK 或已安装依赖能否承担,查其导出与文档,栈内无合适能力再考虑维护良好的库,自写逻辑放最后并说明理由。被拥有的操作只在其承诺效果存在时回答;修复该操作而非增加下游轮询或完成等待器。异步操作可返回已接受的作业,其契约须区分接受与完成。函数签名只声明它真正接收的内容,不要传比参数类型更宽的对象。使用已声明的 API 响应类型。
适配既有代码:新增模式或库前检查仓库现有的错误处理、schema 解析、依赖注入、测试、可观测性、适配器/服务、模块布局。多数应用使用抛出错误与拒绝 Promise,新代码与重构代码沿用。若存在 better-result 依赖(包括传递依赖)并不构成采用 Result 的理由。保留既有日志、追踪、指标与错误上报钩子。
最小差异:改动只触及约定行为所需的行,其余行保持逐字节一致;不移动、重新缩进、重排、改写、重命名或改类型。未经请求的清理须用户同意或确为改进,且作为单独改动。
错误与失败:即使 TypeScript 无法在返回类型声明抛出错误,也要梳理已知失败,使用带稳定标签或代码及有用上下文的类型化错误,绝不从消息文本判断错误类别。实现前明确操作可能产生的失败(含取消与部分完成)、调用方须区分及采取何种行动的失败、哪一层转换外部失败、哪一边界记录最终错误、重复是否安全及谁负责重试、哪些聚焦测试证明重要失败行为与清理。用 @throws 记录不明显的抛出失败。在拥有的适配器处转换外部失败。
Result 的有限使用:仅当显式结果组合带来具体收益(如精细重试分类、多可恢复结果的特殊工作流)时,对孤立操作使用 better-result;状态局部收益并将转换限制在该操作边界,不要为满足本规范扩散到调用方。全应用采用 Result 需单独明确批准。保留既有 Result/Effect 代码。
传输转换:传输适配器使用传输的失败通道,共享中间件负责序列化与错误转换。孤立操作返回 Result 时,适配器可提取成功或把类型化错误抛入既有中间件。改动此契约(含添加服务端桩)前先读后端技能。
缺陷可抛或 panic:生产代码绝不使用 throw new Error(...),改用带稳定标签或代码的类型化错误;触碰到的裸 throw 应迁移,测试与故障探针的故意抛出除外。panic 与缺陷辅助用于使正确执行不可能的条件(违反内部不变量、不可能分支、临时未实现路径、灾难性运行时条件)。已知配置失败用类型化错误,组合根安全上报并终止启动。项目已有共享辅助时使用它们,或使用结果库的 panic 辅助:
export function casesHandled(unexpectedCase: never): never; export function shouldNeverHappen(msg?: string): never; export function notYetImplemented(msg?: string): never;
自定义错误:预期失败用自定义标签错误,一般继承 Error、better-result 的 TaggedError 或 Effect 代码库的 Schema.TaggedErrorClass,包含 'as const' 稳定标签、有用消息、结构化上下文字段、安全遥测字段、可选 cause: unknown。模块边界保持错误联合精确,如 Result<User, UserNotFound | UserStoreUnavailable>;除入口、编排、日志与渲染层外避免宽泛 AppError。
包装与重抛:仅当跨过调用方不应看到内部错误类型的端口时包装,把内部标签映射到调用方已分支的外层标签,保留内层错误为 cause;调用方匹配外层标签,不得解包 cause 决定。仍在拥有内层标签的层、所有内层失败映射为同一外层标签、仅为避免导入内层类型时不要包装。
敏感数据、遥测与调试:优先跨请求、作业、工作流、应用模块、适配器与外部调用的端到端结构化追踪。追踪/日志应能用安全字段诊断失败:领域 ID、操作名、依赖/提供方名、状态标签、重试次数、类型化错误标签、安全摘要。错误、追踪、日志、快照中不得放密钥。对令牌、API 密钥、密码、原始凭据、机密使用 Redacted<T> 包装;Effect 代码库优先 Effect 的 Redacted.Redacted 或本地共享 Redacted<T>。在边界包装敏感值,只在需要原始值处解包。
解析而非校验:边界代码应把未知或弱结构输入转为应用或领域类型后才进入内层。仅当形状或含义差异足够有用时才用独立协议投影。DTO 只描述边界角色,符号名中绝不使用 DTO 或 Dto;按实际协议或持久化含义命名,如 CreateUserRequest、StripeCustomerResponse、UserRecord。流程可为 unknown -> CreateUserRequest -> CreateUserInput -> EmailAddress/UserId,或直接 unknown -> CreateUserInput;不要全应用传递 schema 推断的传输形状。命名:parseX(input): Result<X, ParseXError> 用于不可信或弱结构输入;makeX/createX 用于已类型化部件的智能构造器;isX(value): value is X 为真谓词;assertX 少用;返回精化值的函数避免叫 validateX。
Schema:把 schema 库当边界解析器而非核心逻辑中随意散布的校验器。优先使用仓库既有 schema 库;Effect 代码库用 Effect Schema;通用辅助优先 Standard Schema 兼容;否则优先 Zod 4;小型领域类型可用手写智能构造器/解析器。Zod 4 使用顶层格式辅助:z.uuid()、z.email()、z.url()、z.httpUrl()、z.iso.datetime()、z.iso.date()、z.iso.time(),以及 z.uuidv7()、z.nanoid()、z.ipv4()、z.base64()、z.jwt()、z.stringbool()。用 { error: "message" } 设置消息。类型化 ID 用品牌 ID schema 而非裸 z.string()。
品牌类型与正确构造:当能防止现实误用或非法构造时使用品牌/精化类型,尤其 ID、已解析字符串、受限数字、单位。通过解析器或智能构造器构造品牌值,在有领域类型处避免传原始字符串/数字。需要值处避免 optional/null/undefined,把可选性外推;除非部分性是真实领域概念,避免用 Partial<T> 作应用/领域输入,优先为每个操作写显式输入类型。
一个事实一种表示:每个独立事实只在行、契约、事件、缓存、状态、props、配置中表示一次。保留规范事实或权威结果,确定性值在拥有或消费层派生。添加字段前问:是否独立事实、既有字段能否精确决定、值不一致会怎样、哪个是规范、哪层拥有派生。仅外部兼容、不可变审计快照、实测性能或私有源数据不可得时才允许冗余。识别规范来源,原子同步并尽可能强制不变量,测试不一致情况。
状态机与布尔盲区:实体有有意义生命周期状态时用标签联合或等价类建模,示例:type Invoice = { readonly _tag: "Draft"; readonly id: InvoiceId; readonly lines: NonEmptyArray<LineItem> } | { readonly _tag: "Sent"; readonly id: InvoiceId; readonly sentAt: Instant } | { readonly _tag: "Paid"; readonly id: InvoiceId; readonly paidAt: Instant }; 避免 isSent/isPaid 布尔加可选日期。避免控制行为的布尔参数,如 createUser(input, true),改用命名选项或领域类型,如 createUser(input, { emailVerification: "skip" });布尔作清晰谓词返回值可以,如 isExpired(token): boolean。
模块与抽象:Domain Module、Application Service Module、Adapter Module 命名职责而非文件夹。设计或评审模块、端口、适配器前先读 modules and ports,其中定义各角色、深模块、应用拥有的端口、适配器复用审计与仓库。
函数式内核、命令式外壳与入口点:Domain Module 组成函数式内核,Application Service Module 与 Adapter Module 组成命令式外壳,但只有 Adapter 含技术特定关注点。这样同一应用操作可跨 REST、CLI、GraphQL、worker 等入口复用。函数式内核含领域解析器、不变量、状态转移、计算、组合器与决策函数,避免 I/O、隐藏依赖、环境时间/随机性、抛出的预期失败、框架特定关注点。命令式外壳有两类职责:应用服务应用应用策略并通过显式端口编排效果。
限制:资料未提供具体安装命令、版本要求、收费与价格信息,也未给出可运行代码示例或平台支持说明。规范本身是对代码设计与评审的约束,落地效果取决于项目现有约定与团队采纳程度。