Inngest 团队发现了一个诡异 bug:给 Inngest 客户端添加第二个中间件后,step.run() 的返回类型突然坍缩为 {}。问题的根源是 TypeScript 的 Jsonify<T> 泛型不满足幂等性——当 T 包含可选属性(如 label?: string)时,Jsonify<Jsonify<T>> 会触发分布条件类型的展开反转,导致类型过滤器将其误判为不可序列化。文章抽丝剥茧地展示了 TypeScript 类型体操中最令人头疼的反直觉行为,并提供了基于 HomomorphicMappedType 的修复方案。
Jsonify<T> 的分布条件类型在遇到可选属性时发生展开反转——删除 label?: string 则一切正常[keyof T] 索引访问在映射类型中会产生暗藏的反转行为,对 {a: string; b: never} 取 [keyof T] 得到 "a",但两轮 Jsonify 后可选字段变成 neverHomomorphicMappedType 保持原始对象结构不变,避免分布条件类型的展开ts-toolbelt 的类型展开)逐步追踪类型变换过程