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

如何为Prisma findFirst编写带可选Include的类型安全包装函数?

类型安全封装Prisma关联查询的Nest.js服务方案

问题背景

我用Prisma操作数据库,想在Nest.js服务里封装数据库访问逻辑。Prisma自带完善的类型检查机制,比如关联关系必须显式通过include指定才能访问:

// 编译报错:groups未被加载,无法访问
const user1 = db.user.findFirst({ where: { id: '1' } });
console.log(user1.groups);

// 合法:显式声明include关联后可正常访问
const user2 = db.user.findFirst({ where: { id: '1' }, include: { groups: true } });
console.log(user2.groups);

但封装findFirst时,可选的include参数无法让Prisma正确推导返回类型:

public async getUserById<TInclude extends Prisma.UserInclude>(
  id: string, 
  include?: TInclude
) {
  return db.user.findFirst({
    where: { id /* 其他查询条件 */ },
    include: include,
  });
}

调用时即使传入include,返回类型仍为Promise<User & {} | null>,因为Prisma会基于TInclude | undefined推导类型,忽略了本次调用中include实际存在的情况。

试过拆成两个独立函数(无include、必传include),但写法不够简洁;也试过函数重载,但需要手动编写复杂的返回类型,不想重复Prisma的内置类型逻辑。

最优解决方案

方案1:给include设置默认空对象(推荐)

把include的可选性改成带默认值的必填参数,让TypeScript能根据传入的实际值精准推导返回类型:

public async getUserById<TInclude extends Prisma.UserInclude>(
  id: string, 
  include: TInclude = {} as TInclude
) {
  return db.user.findFirst({
    where: { id /* 其他查询条件 */ },
    include: include,
  });
}

调用方式完全兼容原有习惯:

  • 不传入include:getUserById('1'),返回Promise<User | null>
  • 传入include:getUserById('1', { groups: true }),返回Promise<(User & { groups: Group[] }) | null>

方案2:利用Prisma内置类型工具+条件类型

如果要保留参数的可选性,可借助Prisma的Prisma.UserGetPayload类型工具,结合条件类型手动推导返回类型:

import type { Prisma } from '@prisma/client';

type UserWithInclude<TInclude extends Prisma.UserInclude | undefined> = 
  TInclude extends undefined 
    ? Prisma.UserGetPayload<{}> 
    : Prisma.UserGetPayload<{ include: TInclude }>;

public async getUserById<TInclude extends Prisma.UserInclude | undefined>(
  id: string, 
  include?: TInclude
): Promise<UserWithInclude<TInclude> | null> {
  return db.user.findFirst({
    where: { id /* 其他查询条件 */ },
    include: include,
  }) as Promise<UserWithInclude<TInclude> | null>;
}

该方案保留了include的可选性,同时让返回类型与传入参数正确关联。

方案3:函数重载+复用Prisma返回类型

如果偏好函数重载写法,可利用Prisma的FindFirstUserArgs和UserGetPayload复用内置类型,避免手动维护复杂类型结构:

import type { Prisma } from '@prisma/client';

// 重载1:无include参数
public async getUserById(id: string): Promise<Prisma.UserGetPayload<{}> | null>;
// 重载2:带include参数
public async getUserById<TInclude extends Prisma.UserInclude>(
  id: string, 
  include: TInclude
): Promise<Prisma.UserGetPayload<{ include: TInclude }> | null>;

// 通用实现
public async getUserById<TInclude extends Prisma.UserInclude | undefined>(
  id: string, 
  include?: TInclude
) {
  return db.user.findFirst({
    where: { id /* 其他查询条件 */ },
    include: include,
  });
}

这种方式既保留了简洁的调用体验,又完全复用了Prisma的类型逻辑。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 01:20:37