基于Kysely在TypeScript中实现继承式基础模型的问题求助
基于Kysely实现ActiveRecord风格的基础继承模型
需求
想要构建一个基于继承的基础模型,让子类只需继承基础模型并指定表名,就能拥有类似Rails ActiveRecord的通用CRUD方法,全程只用Kysely,不引入任何ORM。期望的子类结构如下:
import { Exercise } from "../backend/interface/Exercise"; import { BaseModel } from "../BaseModel" export class ExerciseModel extends BaseModel<Exercise> { constructor() { super("exercise"); } }
当前冗余实现
目前每个模型都要重复编写通用CRUD方法,虽然可行但复用性差:
import { Exercise } from "../../interface/Exercise/Exercise"; export class ExerciseModel { static async findById(id: number) { const result = await db .selectFrom('exercise') .where('id', '=', id) .execute() return result[0] as Exercise } static async delete(id: number) { await db.deleteFrom("exercise").where("id", "=", id).execute(); } static async findAll() { const result = await db.selectFrom("exercise").execute(); return result as Exercise[]; } static async update(id: number, name: string) { await db .updateTable("exercise") .set({ name }) .where("id", "=", id) .execute(); } static async create(name: string, muscle_group_id: number) { await db .insertInto("exercise") .values({ name: name, muscle_group_id: muscle_group_id }) .returningAll() .executeTakeFirstOrThrow(); } }
尝试的BaseModel及报错
尝试写了如下BaseModel,但出现类型错误:
export class BaseModel { protected readonly tableName: string; constructor(tablename: string){ this.tableName = tablename } static async findById(id: number) { const result = await db .selectFrom(this.tableName) .where('id', '=', id) .execute() return result[0] } static tableName(tableName: any) { throw new Error("Method not implemented."); } static async delete(id: number) { await db.deleteFrom(this.tableName).where('id', '=', id).execute() } }
错误信息:
Overload 1 of 2, '(from: TableExpression<Models, "exercise">[]): SelectQueryBuilder<From<Models, TableExpression<Models, "exercise">>, any, {}>', gave the following error. Argument of type '(tableName: any) => void' is not assignable to parameter of type 'TableExpression<Models, "exercise">[]'. Overload 2 of 2, '(from: TableExpression<Models, "exercise">): SelectQueryBuilder<From<Models, TableExpression<Models, "exercise">>, any, {}>', gave the following error. Argument of type '(tableName: any) => void' is not assignable to parameter of type 'TableExpression<Models, "exercise">'.
相关配置代码
DB实例创建
import { Kysely, PostgresDialect } from "kysely"; import { Pool } from "pg"; import * as dotenv from 'dotenv' import { Models } from "./models/Models"; dotenv.config() export const db = new Kysely<Models>({ dialect: new PostgresDialect({ pool: new Pool({ connectionString: process.env.DATABASE_URL, }), }), });
Models接口定义
export interface Models{ exercise: Exercise }
解决方案与优化思路
核心问题分析
你写的BaseModel用了静态方法,但静态方法中的this指向类本身而非实例,访问不到实例的tableName属性;同时你定义了同名的静态tableName方法,导致Kysely识别参数类型时出错。
修正后的BaseModel实现
把通用方法改成实例方法,结合泛型保证类型安全:
import { db } from './db'; import { Kysely, Selectable } from 'kysely'; import { Models } from './models/Models'; export class BaseModel<T> { protected readonly tableName: keyof Models; protected readonly db: Kysely<Models>; constructor(tableName: keyof Models) { this.tableName = tableName; this.db = db; } async findById(id: number): Promise<Selectable<T> | undefined> { return this.db .selectFrom(this.tableName) .where('id', '=', id) .selectAll() .executeTakeFirst(); } async delete(id: number): Promise<void> { await this.db .deleteFrom(this.tableName) .where('id', '=', id) .execute(); } async findAll(): Promise<Selectable<T>[]> { return this.db .selectFrom(this.tableName) .selectAll() .execute(); } }
子类使用方式
import { Exercise } from "../backend/interface/Exercise"; import { BaseModel } from "../BaseModel" export class ExerciseModel extends BaseModel<Exercise> { constructor() { super("exercise"); } // 子类扩展专属方法 async create(name: string, muscle_group_id: number): Promise<Exercise> { return this.db .insertInto("exercise") .values({ name, muscle_group_id }) .returningAll() .executeTakeFirstOrThrow(); } async update(id: number, name: string): Promise<void> { await this.db .updateTable("exercise") .set({ name }) .where("id", "=", id) .execute(); } }
额外优化建议
- 强化类型约束:通过泛型关联表名与Models接口,避免传入不存在的表名:
export class BaseModel<TableName extends keyof Models> { protected readonly tableName: TableName; protected readonly db: Kysely<Models>; constructor(tableName: TableName) { this.tableName = tableName; this.db = db; } async findById(id: number): Promise<Selectable<Models[TableName]> | undefined> { return this.db .selectFrom(this.tableName) .where('id', '=', id) .selectAll() .executeTakeFirst(); } }
- 单例模式优化:如果不需要多个模型实例,可改成单例减少重复创建:
export class ExerciseModel extends BaseModel<"exercise"> { private static instance: ExerciseModel; private constructor() { super("exercise"); } public static getInstance(): ExerciseModel { if (!ExerciseModel.instance) { ExerciseModel.instance = new ExerciseModel(); } return ExerciseModel.instance; } // 扩展方法... }
内容的提问来源于stack exchange,提问作者TryingToLearn
相关产品推荐
相关产品推荐

