typescript 一般化省略?

ee7vknir  于 2022-11-26  发布在  TypeScript
关注(0)|答案(2)|浏览(122)

我试图创建一个通用的prisma数据库模型的 Package 器。这个模型只是一个类型化的对象,表示返回的数据库表行。你可以这样想:

type User = {
  user_id: bigint;
  password: string;
  email_address: string;
}

Package 器围绕这些模型提供了一系列实用程序函数,如下所示:从“lodash”导入;

export default class Entity<T extends {}> {
    private readonly cleanModel: T;
    private model: Partial<T>| T;

    constructor(
        model: T,
        guardedProps: string[],
    ) {
        this.cleanModel = model;

        // By default, hide guarded props. Guarded props are only accessible
        // through methods which acknowledge guarded status
        const withoutSensitive: Partial<T> = _.omit(model, guardedProps);
        this.model = withoutSensitive;
    }

    /**
     * Returns the value of the provided key from the model.
     * @param key 
     */
    prop(key: keyof T): any {
        if (key in this.model) {
            return this.model[key];
        }

        throw TypeError(`Key ${String(key)} does not exist on entity Model`);
    }

    guardedProp(key: keyof T): any {
        if (key in this.cleanModel) {
            return this.cleanModel[key];
        }

        throw TypeError(`Key ${String(key)} does not exist on entity Model`);
    }

    /**
     * Picks just the requested keys and returns a new object with those keys.
     * To grab guarded properties, the boolean withGuarded can be passed in.
     * @param props 
     * @param withGuarded 
     * @returns 
     */
    pick(props: (keyof T)[], withGuarded: boolean = false): Partial<T> {
        let picked: Partial<T>  = _.pick(withGuarded ? this.cleanModel : this.model, props);
        return picked;
    }

    toString(): string {
        return this.model.toString();
    }

    toJSON(): Partial<T> | T {
        return this.model;
    }

}

注意model和guardedProps都是Partial类型。我更愿意做的是,让model和guardedProps都是Omit类型,这样我就不必处理Partial的可选性。这将改进IDE完成,并有助于使其不会在日志或API响应中意外泄露敏感信息(如用户密码)。
然而,我似乎找不到一种方法来通用地为实体提供键联合。我愿意为每个模型的每个联合定义类型,但我也找不到一种方法来通用 that
有没有什么方法可以在类上定义一个属性,该属性的类型为键的联合,并将被接受为Omit中的参数,如Omit<T, T["protectedProps"]?我已经尝试了protectedProps: (keyof User)[] = ['password', 'user_id'],它解析得很好,但在实体中导致错误,因为当我尝试前面提到的Omit语法时,keyof T[]无法分配给keyof T类型。

dgjrabp2

dgjrabp21#

我想你在找这个。

class Entity<T, Garded extends string> {
    private readonly cleanModel: T;
    private model: _._Omit<T, Garded>;

    constructor(model: T, guardedProps: Garded[]) {
        this.cleanModel = model;

        // By default, hide guarded props. Guarded props are only accessible
        // through methods which acknowledge guarded status
        const withoutSensitive = _.omit(model, guardedProps);
        this.model = withoutSensitive;
    }

    /**
     * Returns the value of the provided key from the model.
     * @param key 
     */
    prop<K extends keyof _._Omit<T, Garded>>(key: K): _._Omit<T, Garded>[K] {
        if (key in this.model) {
            return this.model[key];
        }

        throw TypeError(`Key ${String(key)} does not exist on entity Model`);
    }

    guardedProp<K extends keyof T>(key: K): T[K] {
        if (key in this.cleanModel) {
            return this.cleanModel[key];
        }

        throw TypeError(`Key ${String(key)} does not exist on entity Model`);
    }

    /**
     * Picks just the requested keys and returns a new object with those keys.
     * To grab guarded properties, the boolean withGuarded can be passed in.
     * @param props 
     * @param withGuarded 
     * @returns 
     */
    pick<K extends keyof T & string>(props: K[], withGuarded: true): _._Pick<T, K>
    pick<K extends keyof T & string>(props: K[], withGuarded?: false): _._Pick<_._Omit<T, Garded>, K>
    pick<K extends keyof T & string>(props: K[], withGuarded: boolean = false) {
        return _.pick(withGuarded ? this.cleanModel : this.model, props);
    }

    toString(): string {
        return this.model.toString();
    }

    toJSON(): _._Omit<T, Garded> {
        return this.model;
    }

}
  • underline * 有它自己的_Pick_Omit类型,我没有费心去弄清楚它们之间的区别,但是它们似乎与标准的实用程序类型不兼容,所以你不得不使用它们。

我注意到pick的一个返回类型中包含Partial,我想您可以安全地关闭它,因为Entity的构造是穷举的。

pod7payv

pod7payv2#

在看到geoffrey的答案后,我花了几个小时来研究这个问题,虽然在类中添加省略的键对输入有用,但它只得到了我需要的类型,在运行或编译时没有足够的信息来帮助我缩小类型范围。
理想情况下,我也不想每次需要一个实体示例时都传入大量类型。所以,如果有人想自己做类似的事情,我最终得到了以下结果。
用户模型

type User = {
  user_id: bigint;
  password: string;
  email_address: string;
}

Model类(替换Entity),作为要扩展的基类。

export class Model<T extends {}, GuardedT extends {}, TGuarded extends {}> {
    private cleanModel: T;

    model: GuardedT;
    guarded: TGuarded;

    constructor(model: T, guardedProps: (keyof T)[]) {
        // Create a clean version of the prisma model as it is now. This will help
        // determine if changes are made later. 
        this.cleanModel = _.cloneDeep(model);

        // Separate the guarded props from the rest.
        // Any is used here because we will gradually build up to the expected
        // types.
        const props: any = {};
        const guarded: any = {};

        for (const prop in model) {
            if (guardedProps.includes(prop)) {
                guarded[prop] = model[prop];
            } else {
                props[prop] = model[prop];
            }
        }

        this.model = props as GuardedT;
        this.guarded = guarded as TGuarded;
    }

    toJSON(): GuardedT {
        return this.model;
    }

    toString(): string {
        return this.model.toString();
    }

    /**
     * Picks just the requested keys and returns a new object with those keys.
     * Should be used where possible for network transmission of model data.
     * @param props 
     * @returns 
     */
    pick(props: (keyof T)[]): Partial<T> {
        return _.pick(this.cleanModel, props);
    }
    
}

为了使用它,定义了一个表示基本模型的新类,如下图所示。我还定义了表示受保护模型的类型,并且只定义了受保护的属性。

export type UserGuard = Omit<User, "password">;
export type UserGuarded = Pick<User, "password">;

const guarded: (keyof User)[] = ['password'];

export class UserModel extends Model<User, UserGuard, UserGuarded>{
    constructor(model: User) {
        super(model, guarded);
        this.model = model;
    }
}

现在我可以简单地将new UserModel(returnedModelFromPrisma)类化。

相关问题