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

使用Kysely编写通用Repository时的TypeScript类型报错问题

Kysely通用Repository泛型类型错误解决:string类型不匹配OperandValueExpressionOrList

问题重现

在编写Kysely通用Repository时,findOne方法的where("uuid", "=", id)语句触发类型错误:

Argument of type 'string' is not assignable to parameter of type 'OperandValueExpressionOrList<Tables, ExtractTableAlias<Tables, TableName>, "uuid">'.(2345)

相关代码示例:

import { Kysely, Selectable } from "kysely";

export type FooTable = {
  uuid: string;
  name: string;
  fromFoo: boolean;
};

export type BarTable = {
  uuid: string;
  name: string;
  fromBar: string;
}

type Tables = {
  foo: FooTable;
  bar: BarTable;
};

type Data = {
  uuid: string;
  name: string;
  [key: string]: unknown;
}

export interface GenericRepository<T extends Data> {
  findOne(id: Data["uuid"]): Promise<T | null>;
}

export function createGenericRepository<
  D extends Data,
  TableName extends keyof Tables,
  T extends Tables[TableName]
>({
  dataSource,
  tableName,
  toModel,
}: {
  dataSource: Kysely<Tables>;
  tableName: TableName;
  toModel: (data: Selectable<T>) => D;
}): GenericRepository<D> {
  return {
    async findOne(id) {
      const data = await dataSource
        .selectFrom(tableName)
        .selectAll()
        .where("uuid", "=", id) // 此处报错
        .executeTakeFirst();

      if (!data) {
        return null;
      }

      return toModel(data);
    }
  };
}

function createFooRepository(dataSource: Kysely<Tables>) {
  return createGenericRepository({
    dataSource,
    tableName: "foo",
    toModel: (data) => { return { uuid: "123", name: "name", fromFoo: true } }
  })
}

错误原因

Kysely的where方法对参数类型有严格的推导逻辑,当前泛型定义存在两个问题:

  1. 冗余的泛型参数T extends Tables[TableName]导致TypeScript无法精准关联表字段类型与传入的id类型;
  2. GenericRepository中findOne的id类型固定为Data["uuid"],未与具体表的uuid字段类型绑定,导致类型不匹配。

解决方案

方案1:简化泛型+类型断言

调整泛型参数结构,移除冗余定义,并通过类型断言让TypeScript明确id与表uuid字段的类型匹配:

import { Kysely, Selectable } from "kysely";

export type FooTable = {
  uuid: string;
  name: string;
  fromFoo: boolean;
};

export type BarTable = {
  uuid: string;
  name: string;
  fromBar: string;
}

type Tables = {
  foo: FooTable;
  bar: BarTable;
};

type Data = {
  uuid: string;
  name: string;
  [key: string]: unknown;
}

export interface GenericRepository<T extends Data> {
  findOne(id: T["uuid"]): Promise<T | null>;
}

export function createGenericRepository<
  TableName extends keyof Tables,
  D extends Data
>({
  dataSource,
  tableName,
  toModel,
}: {
  dataSource: Kysely<Tables>;
  tableName: TableName;
  toModel: (data: Selectable<Tables[TableName]>) => D;
}): GenericRepository<D> {
  return {
    async findOne(id) {
      const data = await dataSource
        .selectFrom(tableName)
        .selectAll()
        .where("uuid", "=", id as Tables[TableName]["uuid"])
        .executeTakeFirst();

      if (!data) {
        return null;
      }

      return toModel(data);
    }
  };
}

function createFooRepository(dataSource: Kysely<Tables>) {
  return createGenericRepository({
    dataSource,
    tableName: "foo",
    toModel: (data) => ({ uuid: data.uuid, name: data.name, fromFoo: data.fromFoo })
  })
}

方案2:严格泛型约束(无需断言)

通过泛型约束直接绑定表类型与模型的uuid类型,完全满足Kysely的类型要求:

import { Kysely, Selectable } from "kysely";

export type FooTable = {
  uuid: string;
  name: string;
  fromFoo: boolean;
};

export type BarTable = {
  uuid: string;
  name: string;
  fromBar: string;
}

type Tables = {
  foo: FooTable;
  bar: BarTable;
};

type Data = {
  uuid: string;
  name: string;
  [key: string]: unknown;
}

export interface GenericRepository<T extends Data> {
  findOne(id: T["uuid"]): Promise<T | null>;
}

export function createGenericRepository<
  TableName extends keyof Tables,
  TableType extends Tables[TableName],
  D extends Data & { uuid: TableType["uuid"] }
>({
  dataSource,
  tableName,
  toModel,
}: {
  dataSource: Kysely<Tables>;
  tableName: TableName;
  toModel: (data: Selectable<TableType>) => D;
}): GenericRepository<D> {
  return {
    async findOne(id) {
      const data = await dataSource
        .selectFrom(tableName)
        .selectAll()
        .where("uuid", "=", id)
        .executeTakeFirst();

      if (!data) {
        return null;
      }

      return toModel(data);
    }
  };
}

function createFooRepository(dataSource: Kysely<Tables>) {
  return createGenericRepository({
    dataSource,
    tableName: "foo",
    toModel: (data) => ({ uuid: data.uuid, name: data.name, fromFoo: data.fromFoo })
  })
}

关键调整点

  • 移除冗余泛型参数,减少类型推导复杂度;
  • 让findOne的id类型与模型/表的uuid字段类型绑定;
  • 修正toModel实现,避免硬编码返回值,保证类型一致性。

内容的提问来源于stack exchange,提问作者Guid

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 17:24:55