搜索/用服务器和客户端共用的 API 模式构建全栈 TypeScript 应用

用服务器和客户端共用的 API 模式构建全栈 TypeScript 应用

在构建或修改全栈 TypeScript 应用时使用。AI 会为路由和客户端钩子只定义一次每个 API 请求和响应,在读取输入前先做身份验证,保持依赖、缓存和组件样式一致,并编写快速测试。

要使用这个AI规则
## 让包依赖保持单向,并确定各类状态的存放位置 组织代码库,使所有权、依赖方向和状态放置方式在系统增长过程中始终可预测。 ### 按职责组织结构 - 让每个组件、钩子、存储、服务和包都只有一个明确用途;相比向已经复杂的单元添加分支和模式,应优先组合小型单元。 - 在依赖图中,将应用程序代码置于可复用包之上;较低层级的包不得导入应用程序模块。 - 使隔离服务不包含无关的 UI 框架、注册表、提供方 SDK 和执行引擎。 - 通过一个兼容的会话和数据模型共享身份验证,而不要在每个应用程序中重新实现。 ### 保持类型与状态的所有权 - 为组件属性、状态、函数输入和返回值定义明确的接口。 - 不要使用 `any`;当类型确实不确定时,应使用带类型守卫的 `unknown`。 - 将服务器状态保存在服务器状态库中,将全局客户端状态保存在存储中,将本地展示状态保存在组件中。 - 将复杂存储拆分为实现模块和类型模块;只持久化必须在重新加载后保留的状态,并明确选择要持久化的字段。 ## 使用仓库共享的辅助工具和工具链,不另写本地副本 使用一套规范工具链和共享实现模式,不要创建局部替代品。 ### 应用一致的代码惯例 - 使用仓库选定的包管理器、格式化程序、代码检查器、类型检查器和审计命令。 - 使用结构化日志而不是控制台输出;按请求作用域工作的路由包装器应自动附加请求标识符。 - 遵循一致的命名和导入顺序;使用仅类型导入、常量断言、明确的引用元素类型和 TSDoc,而不要使用装饰性或无结构的注释。 - 优先使用绝对导入。只有当文件夹至少有3个导出时才创建 barrel,并且不要从非 barrel 文件重新导出。 ### 复用经过验证的辅助工具 - 对 ID 生成、延迟、错误规范化、消息提取、深度克隆、对象筛选、截断和带抖动的重试退避使用共享辅助工具;不要用局部包或一次性逻辑重新创建它们。 - 由1个使用方调用的辅助工具应内联;当至少2个文件需要它时,再移入实用工具模块。 - 当组件超过约50行、服务于至少2个调用方,或拥有状态或逻辑时,应将其提取出来;仅用于展示且单次使用、少于10行的片段应保持内联。 ## 每个 API 请求和响应只定义一次,并为每个原始 fetch 写明理由 只定义一次每个 HTTP 请求和响应,使路由与客户端不会逐渐偏离为不兼容的线路格式。 ### 将契约作为边界事实来源 - 每个资源系列保留一个契约模块,为路径参数、查询值、正文、标头和响应分别设置模式,再将它们组合为一个路由契约。 - 为客户端导出具名模式以及具名输入和输出类型别名,并复用共享标识符原语,不要推导临时线路类型或重新定义基于字符串的标识符。 - 可以允许领域验证器直接使用模式库,但应使路由边界模式脱离路由处理程序和客户端。 ### 明确控制例外 - 任何例外的 raw fetch、双重类型转换、原始 JSON 读取或有意不指定类型的响应,都必须在紧邻其上的位置附带说明理由的注释。 - 只允许在流式传输、二进制传输、多部分上传、签名 URL、OAuth 重定向或外部来源等情况下使用 raw fetch。 - 只允许对无法使用标准解析器的信封或宽容解析进行原始 JSON 读取;只允许对真正不透明的数据使用无类型响应。 - 将合理的整文件例外放入经过审查的允许列表,不要把逐行绕过措施散布在代码中。 - 如果缺少理由、存在路由局部模式、原始读取、未经批准的 fetch 或不安全的双重类型转换,应让严格边界审计失败。 ## 从模式开始,依次添加 API 路由和客户端钩子 按照固定顺序添加边界功能,使身份验证、验证、响应类型和缓存端到端保持一致。 ### 遵循实现顺序 1. 编写契约及其具名输入和输出类型。 2. 使用规范的请求上下文和错误处理包装器封装路由,并组合共享的身份验证、速率限制和访问控制中间件,不要在每个路由中重新实现。 3. 在解析不受信任的输入之前完成身份验证和授权,然后通过契约解析请求的所有部分。 4. 严格返回响应模式声明的 JSON 结构。 5. 添加类型化客户端钩子,通过同一契约发送并验证数据。 6. 在组件中使用该钩子,并显示其类型化错误消息。 ### 像审查迁移一样审查模式 - 区分省略与 `null`;将 optional 与 nullable 组合会产生第三种状态,必须有意为之。 - 将每个响应调用位置与声明的响应模式进行比较。 - 为字符串、数组、限制和大小设置明确边界,并提供针对具体字段的描述性错误。 - 将 unknown 模式视为例外,并记录有效载荷为何不透明;当线路格式包含判别字段时,应优先使用可判别联合。 - 使用人工审查判断语义质量;结构审计无法发现所有薄弱边界或不准确的响应结构。 ## 在取消和乐观更新后仍保持查询缓存正确 明确处理取消、缓存、失效和乐观更新,不要把服务器状态隐藏在组件状态中。 ### 构建稳定的钩子和查询键 - 当稳定回调必须读取最新输入时,使用引用,并在输入变化时更新该引用。 - 从每个查询函数转发取消信号,并为每个查询设置明确的新鲜度间隔。 - 从根键到列表和详情前缀构建分层键,以便进行定向失效;只为键可变的查询保留之前的数据,不要为静态查询保留。 ### 安全地协调变更 - 在乐观更新之前,取消受影响的查询并保存之前的缓存值,以便发生错误时恢复。 - 在操作结束时进行协调和失效,而不只是在成功时进行,因为成功和失败都要求缓存保持一致。 - 优先进行定向失效,而不是让整个资源树失效。 - 不要无谓地把稳定的变更方法或对象加入回调依赖项。 - 同源 JSON 应通过类型化请求辅助工具处理;raw fetch 例外必须遵循已记录的边界政策。 ## 共享组件的外观由组件自己决定,并通过属性调整 将共享组件的外观视为由组件自身拥有的单一事实来源,而不是由各使用方分别拥有。 ### 保持展示局部且可组合 - 不要为局部功能修改全局样式;将样式限定在组件内。 - 使用项目的条件类辅助工具和尺寸简写,不要重复类构建逻辑。 - 只有当组件至少有2个真正的变体时才使用变体工具;对于一个状态开关,使用简单的条件类。 - 通过受支持的公共入口点导入共享组件,而不是通过内部子路径。 - 验证引用的 CSS 变量是否存在,因为未定义的变量可能会悄然回退到非预期颜色。 ### 通过属性表达意图 - 为错误、装饰元素、标签、提示、必填状态和行为模式公开属性;使用方的类覆盖应保留给布局和尺寸,而不是用于规范的边框、填充、圆角、高度、颜色或焦点样式。 - 对模式使用可判别联合属性,不要维护近乎重复的组件。 - 在模态框正文中使用规范的带标签字段包装器,使字段间距与页眉和页脚对齐。 - 迁移后移除旧变体,不要保留并行的设计系统路径。 ## 用共享的模拟和伪计时器编写快速的 Vitest 测试 使用仓库的标准测试运行器、全局夹具和确定性计时模式。 ### 隔离行为 - 使用仓库标准的测试后缀,在源文件旁为每个测试命名。 - 复用全局模拟和共享工厂,仅当测试需要不同的行为时才替换其中一个;在每个测试前清除模拟状态,并明确设定每个测试的返回值。 - 当测试不涉及重量级注册表和提供方依赖项时,对它们进行模拟。 - 除非需要浏览器 API,否则使用服务器运行时环境。 - 避免使用真实计时器;使用伪计时器或该行为所需的最小确定性延迟。 ### 避免代价高昂的模块更替 - 在 Vitest 中,优先使用提升的模拟和静态导入。 - 不要组合模块重置、后期模拟和动态导入;当明确的模拟已经足够时,不要通过 `importActual` 导入真实实现。 - 保持身份验证和日志模拟明确,不要调用隐藏后期模拟的辅助工具。 - 在审查前,将严格 API 边界审计与相关测试一同运行。 ## 添加集成时,在变量解析前不转换类型 按依赖顺序构建集成,并将未解析引用保留到执行阶段。 ### 遵循集成顺序 1. 使用稳定的 `snake_case` 标识符定义并注册工具。 2. 添加使用方块,并按规范顺序注册。 3. 添加图标和所需元数据。 4. 只有集成需要时才添加触发器。 5. 根据权威 API 文档验证服务行为。 ### 遵守生命周期时序 - 区分序列化时配置和执行时参数解析。 - 序列化未解析的值时绝不要强制转换类型;这可能会破坏动态引用。 - 只在执行期间变量解析完成后进行数值及其他类型转换。 - 保持工具、块、触发器、身份验证和文件输入元数据同步。 - 运行代码检查、类型检查、测试和严格边界审计,然后审查最终差异中的契约、缓存、样式和注册表一致性。