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

基于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();
  }
}

额外优化建议

  1. 强化类型约束:通过泛型关联表名与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();
  }
}
  1. 单例模式优化:如果不需要多个模型实例,可改成单例减少重复创建:
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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 22:13:19