You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TypeScript中CanUpdateOne派生类型在Validators工具类型中失效问题

问题描述

我正在为团队开发一款简化CRUD API创建的微框架,定义了CanFindAll、CanFindOne、CanCreateOne、CanReplaceOne、CanUpdateOne和CanDeleteOne等可增量添加的Repository操作类型。编写Validators工具类型用于根据传入的仓库类型生成对应的验证器类型时,所有操作均正常工作,唯独CanUpdateOne出现异常:直接获取该类型的参数为预期的{ message?: string, id: string },但通过Validators获取的实际类型却是{ id: string }。

相关类型定义如下:

// 操作类型定义(省略部分重复注释)
export type CanUpdateOne<
    DTO extends Record<'id', unknown>,
    CanRead extends keyof DTO,
    CanWrite extends keyof DTO,
> = Record<
    'updateOne',
    (
        data: Prettify<Partial<Pick<DTO, CanWrite>> & {
            id: DTO["id"];
        }>
    ) => Promise<Option<Prettify<Pick<DTO, CanRead>>>>
>

export type Validators<Repo> = {
    [K in keyof Repo]-?: Repo[K] extends CanUpdateOne<
        infer DTO,
        infer CanRead,
        infer CanWrite
    >['updateOne']
    ? (
        req: Request,
    ) => Parameters<
        CanUpdateOne<DTO, CanRead, CanWrite>['updateOne']
    >[0]
    // 其他操作分支省略
    : never
}

基础类型:

export type DTO = {
    id: string
    message: string
    createdAt: Date
    updatedAt: Date
}

export type CanRead = keyof DTO
export type CanWrite = 'message'

测试代码:

const repo: CanUpdateOne<DTO, CanRead, CanWrite> = {
    updateOne: async (data) => none()
}

type Actual = ReturnType<Validators<typeof repo>['updateOne']> // { id: string }
type Expected = Parameters<
    CanUpdateOne<DTO, CanRead, CanWrite>['updateOne']
>[0] // { message?: string, id: string }
问题原因

TypeScript在条件类型中反向推断CanUpdateOne的泛型参数时,无法准确还原Partial<Pick<DTO, CanWrite>>的结构。当从Repo[K](即updateOne函数)反向推断DTO、CanRead和CanWrite时,Partial<Pick<DTO, CanWrite>>被TypeScript推断为{}(可选属性在反向类型推断中容易被忽略),导致交叉后的类型只剩{ id: string }。

解决方案

修改Validators类型,直接从目标函数中推断参数类型,而非通过重新构造CanUpdateOne类型来获取参数:

export type Validators<Repo> = {
    [K in keyof Repo]-?: Repo[K] extends (data: infer P) => unknown
    ? (req: Request) => P
    // 其他操作分支保留(如果需要区分不同操作类型,可在此基础上添加条件判断)
    : never
}

如果需要保持对不同操作类型的区分(比如后续要针对不同操作做差异化处理),可以调整条件分支,先匹配操作类型,再直接提取函数参数:

export type Validators<Repo> = {
    [K in keyof Repo]-?: 
        // 先匹配CanUpdateOne的函数类型
        Repo[K] extends CanUpdateOne<any, any, any>['updateOne']
        ? (req: Request) => Parameters<Repo[K]>[0]
        // 其他操作分支示例
        : Repo[K] extends CanCreateOne<any, any>['createOne']
        ? (req: Request) => Parameters<Repo[K]>[0]
        : never
}

修改后,Actual类型会和Expected一致,正确保留message?: string的可选属性。

内容的提问来源于stack exchange,提问作者Edén Ariché

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 13:22:15