You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Drizzle ORM仓库模式封装中的TypeScript类型错误问题咨询

Drizzle ORM仓库模式封装中的TypeScript类型错误问题咨询

问题回顾

你封装的这个Drizzle仓库模式思路很清晰,核心是通过泛型复用CRUD逻辑,但TypeScript在推导returning()结果类型时出了问题——返回的{ [x: string]: unknown; }无法匹配我们期望的InferSelectModel<TTable>类型。我之前也踩过类似的Drizzle泛型类型坑,下面来拆解问题和解决方法:

错误原因

这个类型不匹配本质是AnyPgTable的约束太宽泛了。Drizzle的类型系统依赖表的具体列结构来推导类型,但AnyPgTable是一个通用的“任意PostgreSQL表”类型,TypeScript无法从中精准解析出当前表的列名、数据类型、是否可为空等细节,导致returning()只能返回最模糊的unknown结构类型,自然无法赋值给Row

解决方案

我整理了几种从安全到便捷的解决方式,你可以根据需求选择:

1. 用更精确的泛型约束(推荐)

AnyPgTable替换为PgTable(从drizzle-orm/pg-core导入),它是PostgreSQL表的具体类型,能让TypeScript精准推导表的列结构:

import { InferInsertModel, InferSelectModel } from 'drizzle-orm';
// 替换AnyPgTable为PgTable
import { PgTable } from 'drizzle-orm/pg-core';
import { drizzle } from 'drizzle-orm/node-postgres';
import { Pool } from 'pg';

const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const db = drizzle(pool);

export function createRepository<TTable extends PgTable>(table: TTable) {
  type Row = InferSelectModel<TTable>;
  type Data = InferInsertModel<TTable>;

  return {
    async create(data: Data): Promise<Row> {
      const [result] = await db.insert(table).values(data).returning();
      return result; // 现在类型推导完全正常
    },
  };
}

这种方法不需要任何类型断言,完全依赖TypeScript的自动推导,类型安全性拉满,是最优解。

2. 显式断言结果类型(便捷方案)

如果暂时不想调整泛型约束,也可以显式将returning()的结果断言为Row数组——因为我们明确知道插入后返回的行就是InferSelectModel<TTable>的结构,这个断言是安全的:

async create(data: Data): Promise<Row> {
  const [result] = await db.insert(table).values(data).returning() as Promise<Row[]>;
  return result;
}

3. 显式指定返回列(兜底方案)

如果上面两种方法仍有问题,可以直接把表的所有列传给returning(),强制Drizzle推导精确的返回类型:

async create(data: Data): Promise<Row> {
  const [result] = await db.insert(table).values(data).returning(table);
  return result;
}

扩展建议

如果后续要给仓库加更多方法(比如查询、更新),可以继续利用Drizzle的类型工具保持类型安全。比如加一个根据主键查询的方法:

export function createRepository<TTable extends PgTable & { id: { _: { data: infer IdType } } }>(table: TTable) {
  type Row = InferSelectModel<TTable>;
  type Data = InferInsertModel<TTable>;
  type PrimaryKeyType = IdType;

  return {
    async create(data: Data): Promise<Row> {
      const [result] = await db.insert(table).values(data).returning();
      return result;
    },
    async findById(id: PrimaryKeyType): Promise<Row | undefined> {
      const [result] = await db.select().from(table).where(table.id.eq(id)).limit(1);
      return result;
    },
  };
}

这里通过泛型约束强制表必须有id主键列,让findById方法也能享受到类型检查。

这样整个仓库模式既实现了DRY,又完全保留了TypeScript的类型安全,用起来会很顺手~

火山引擎 最新活动