讲师中心 微信公众号
AI工具推荐 视频效率加速

如何优雅地提取和管理Prisma客户端扩展类型

老磊小哥_8676

老磊小哥_8676

发布时间:2025-11-10 17:13:01

|

823人浏览过

|

来源于php中文网

原创

如何优雅地提取和管理prisma客户端扩展类型

本文旨在解决Prisma客户端扩展在模块化时遇到的类型提取难题。通过深入探讨TypeScript的`Parameters`和`Extract`工具类型,我们展示了一种高效的方法来精确定义和分离Prisma客户端扩展的类型,从而提升代码的可维护性和可读性,避免直接在`$extends`方法内部定义复杂类型。

理解Prisma客户端扩展及其类型挑战

Prisma客户端扩展(Client Extensions)是Prisma提供的一项强大功能,允许开发者在不修改Prisma核心客户端的情况下,向其添加自定义逻辑、查询钩子或计算属性。这对于实现业务特定的数据操作、审计日志、权限控制等场景非常有用。

然而,当尝试将这些扩展逻辑模块化到单独的文件中以提高代码可维护性时,开发者常常会遇到类型定义的挑战。Prisma的$extends方法接受一个配置对象,其内部的类型结构会根据具体的模型和操作变得非常复杂。直接从_prismaClient.$extends方法中提取特定扩展部分的类型,并将其应用于独立的模块,并非易事。例如,以下是一个典型的Prisma客户端扩展定义:

// 假设 _prismaClient 是原始的 PrismaClient 实例
const prismaClient = _prismaClient.$extends({
  query: {
    company: {
      update: async ({ args, query }) => {
        // 业务逻辑:如果公司状态为DECLINED,则锁定相关用户账户
        if (args.data?.status === CompanyStatus.DECLINED) {
          args.data.user = {
            update: {
              accountLocked: AccountLockedReason.COMPANY_DECLINED,
            },
          };
        }
        return query(args);
      },
    },
  },
});

当尝试将company模型的update扩展逻辑分离到一个独立的文件companyExtensions.ts中时,我们需要为其定义正确的类型,以便在主prismaClient.ts文件中引用:

// prismaClient.ts
import { companyExtensions } from './companyExtensions';

const prismaClient = _prismaClient.$extends({
  query: {
    company: companyExtensions, // 需要 companyExtensions 具有正确的类型
  },
});

// companyExtensions.ts
// export const companyExtensions: NeedsType = { ... }; // 这里的 NeedsType 是挑战

直接从prismaClient['$extends']的类型中推断出companyExtensions的精确类型,通常会导致一个庞大且难以理解的类型定义,因为它包含了所有可能的扩展点。

解决方案:利用TypeScript工具类型精确提取

解决此问题的关键在于巧妙地结合使用TypeScript的内置工具类型:Parameters和Extract。

  1. Parameters: 这个工具类型用于获取函数类型的所有参数类型,并以元组的形式返回。[0]则表示获取第一个参数的类型。对于_prismaClient.$extends方法,其第一个参数正是我们传递的整个扩展配置对象。

  2. Extract: 这个工具类型用于从Type中提取所有可分配给Union的成员。在这里,我们将使用它来从复杂的扩展配置类型中,筛选出符合我们期望结构(例如,包含name属性,尽管此处name是可选的,但其存在有助于区分不同的扩展配置结构)的部分。

将这两者结合起来,我们可以得到一个简洁且准确的类型定义:

type ExtensionArgs = Extract<
  Parameters<typeof _prismaClient.$extends>[0],
  { name?: string }
>;

这里的{ name?: string }作为一个“标记”或“模式匹配器”,帮助Extract工具类型从$extends方法的第一个参数类型(一个复杂的联合类型或交集类型)中,筛选出那些符合我们通常用来定义客户端扩展的结构。虽然name属性在实际的扩展配置中并非强制,但它提供了一个有效的模式来匹配Prisma扩展的顶层结构。

Scrapfly SDK TypeScript
Scrapfly SDK TypeScript

使用 Scrapfly SDK(scrapfly-sdk)在 TypeScript 或 JavaScript 中进行网页抓取,用于用户请求抓取 URL、绕过反爬虫防护、渲染页面等场景。

下载

实施步骤与示例代码

现在,我们可以将上述ExtensionArgs类型应用于我们的模块化扩展定义中。

1. 定义共享类型(推荐在单独的类型文件中)

创建一个types.ts或类似的类型定义文件:

// src/types/prisma.ts (或任何你喜欢的路径)
import { PrismaClient } from '@prisma/client';

// 假设 _prismaClient 是你原始的 PrismaClient 实例。
// 注意:为了在类型文件中引用它,你可能需要一个“虚拟”实例或一个类型别名。
// 最简单的方法是直接从 @prisma/client 导入 PrismaClient 类型。
// 如果你有一个自定义的基类,可能需要调整。
// 这里我们假设 _prismaClient 是 PrismaClient 的一个实例。
// 如果你想避免实际导入 _prismaClient,可以这样定义一个类型:
// type BasePrismaClient = InstanceType<typeof PrismaClient>;
// 然后在 ExtensionArgs 中使用 BasePrismaClient['$extends']
// 但为了简化,我们直接使用 _prismaClient 的类型。

// 假设你有一个 _prismaClient 实例,或者你可以直接使用 typeof PrismaClient
// 这里的 _prismaClient 应该指向你实际使用的 PrismaClient 实例或其类型
// 示例:如果你有一个 const _prismaClient = new PrismaClient();
// 那么 typeof _prismaClient 就是正确的。
// 如果你只是想定义类型,可以这样模拟:
declare const _prismaClient: PrismaClient; // 这是一个类型声明,不会生成运行时代码

export type ExtensionArgs = Extract<
  Parameters<typeof _prismaClient.$extends>[0],
  { name?: string } // 使用 name?: string 作为模式匹配,匹配Prisma扩展的顶层结构
>;

2. 模块化你的客户端扩展

在companyExtensions.ts文件中,使用ExtensionArgs来定义你的扩展对象。

// src/extensions/companyExtensions.ts
import { CompanyStatus, AccountLockedReason } from '@prisma/client'; // 假设这些枚举已定义
import { ExtensionArgs } from '../types/prisma'; // 导入定义的类型

// 精确指定 company 模型的 query.update 扩展类型
// 我们可以通过 ExtensionArgs 进一步推断出具体模型的扩展类型
// 实际操作中,Prisma的类型推断通常足够智能,
// 但为了明确和分离,我们可以在这里直接应用更通用的 ExtensionArgs
// 然后让TypeScript在组合时进行验证。

// 这里的 companyExtensions 必须符合 ExtensionArgs 的部分结构
// 更精确的做法是只导出 query.company 的部分
export const companyExtensions: ExtensionArgs['query']['company'] = {
  update: async ({ args, query }) => {
    if (args.data?.status === CompanyStatus.DECLINED) {
      args.data.user = {
        update: {
          accountLocked: AccountLockedReason.COMPANY_DECLINED,
        },
      };
    }
    return query(args);
  },
};

3. 组合客户端扩展

在主prismaClient.ts文件中,导入并使用这些模块化的扩展。

// src/prismaClient.ts
import { PrismaClient } from '@prisma/client';
import { companyExtensions } from './extensions/companyExtensions';

const _prismaClient = new PrismaClient();

export const prismaClient = _prismaClient.$extends({
  query: {
    company: companyExtensions, // 类型现在可以正确推断和验证
  },
  // 可以继续添加其他模型的扩展
});

export type ExtendedPrismaClient = typeof prismaClient;

通过这种方式,companyExtensions对象现在拥有了明确的类型定义,并且与_prismaClient.$extends方法的期望完全匹配。这大大提高了代码的可读性和可维护性,使得团队成员可以更容易地理解和修改独立的扩展逻辑,而无需深入分析复杂的Prisma内部类型。

注意事项与最佳实践

  • _prismaClient的类型来源: 在ExtensionArgs的定义中,typeof _prismaClient至关重要。确保它指向你项目中实际使用的PrismaClient实例的类型。如果你在类型文件中无法直接访问一个运行时实例,可以声明一个declare const _prismaClient: PrismaClient;来提供类型信息。
  • name?: string的用途: Extract中的{ name?: string }是一个巧妙的技巧。Prisma客户端扩展的配置对象,尤其是通过defineExtension创建的,通常会有一个可选的name属性。即使你的匿名扩展没有显式设置name,这个模式匹配也足以帮助Extract筛选出正确的顶层扩展配置类型。
  • 细化类型: 虽然ExtensionArgs提供了顶层类型,但你可能希望为更深层次的扩展(例如query.company)创建更具体的类型。在companyExtensions.ts中,我们使用了ExtensionArgs['query']['company']来进一步细化,这使得类型定义更加精确和安全。
  • Prisma defineExtension: Prisma也提供了defineExtension函数,主要用于创建可分发的通用扩展。虽然它能帮助定义扩展,但对于特定于应用模型的细粒度args类型,可能不如直接使用$extends结合Parameters/Extract来得灵活和直接。

总结

通过巧妙运用TypeScript的Parameters和Extract工具类型,我们可以有效地从Prisma客户端的$extends方法中提取出精确的扩展配置类型。这使得将复杂的客户端扩展逻辑模块化成为可能,极大地提升了大型Prisma项目中的代码组织、可读性与可维护性。这种方法不仅解决了类型定义上的挑战,也促进了更清晰的代码结构和团队协作效率。

热门AI工具

更多
UpDream
UpDream Hot

一款AI视频创作工具,主要用于哔哩哔哩推出的自研AI视频创作工具,适合需要提升相关任务效率的用户。

蛙蛙写作

一款AI论文写作工具,主要用于超级AI智能写作助手,适合需要提升相关任务效率的用户。

超级简历WonderCV

一款AI办公效率工具,主要用于免费求职简历模版下载制作,应届生职场人必备简历制作神器,适合需要提升相关任务效率的用户。

Loomy
Loomy Hot

一款AI工具,主要用于科大讯飞发布的桌面级 AI 助理,比 OpenClaw 更易用、更安全!,适合需要提升相关任务效率的用户。

UP简历
UP简历 Hot

一款AI办公效率工具,主要用于基于AI技术的免费在线简历制作工具,适合需要提升相关任务效率的用户。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

SkildArt
SkildArt Hot

SkildArt是一款AI文本写作工具,一站式 AI 视觉创作平台。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

相关专题

更多
TypeScript工程化开发与Vite构建优化实践
TypeScript工程化开发与Vite构建优化实践

本专题面向前端开发者,深入讲解 TypeScript 类型系统与大型项目结构设计方法,并结合 Vite 构建工具优化前端工程化流程。内容包括模块化设计、类型声明管理、代码分割、热更新原理以及构建性能调优。通过完整项目示例,帮助开发者提升代码可维护性与开发效率。

312

2026.02.13

TypeScript全栈项目架构与接口规范设计
TypeScript全栈项目架构与接口规范设计

本专题面向全栈开发者,系统讲解基于 TypeScript 构建前后端统一技术栈的工程化实践。内容涵盖项目分层设计、接口协议规范、类型共享机制、错误码体系设计、接口自动化生成与文档维护方案。通过完整项目示例,帮助开发者构建结构清晰、类型安全、易维护的现代全栈应用架构。

520

2026.02.25

TypeScript类型系统进阶与大型前端项目实践
TypeScript类型系统进阶与大型前端项目实践

本专题围绕 TypeScript 在大型前端项目中的应用展开,深入讲解类型系统设计与工程化开发方法。内容包括泛型与高级类型、类型推断机制、声明文件编写、模块化结构设计以及代码规范管理。通过真实项目案例分析,帮助开发者构建类型安全、结构清晰、易维护的前端工程体系,提高团队协作效率与代码质量。

391

2026.03.13

TypeScript 全栈开发进阶指南
TypeScript 全栈开发进阶指南

面向有 JavaScript 基础的开发者,深入讲解 TypeScript 的类型系统与全栈开发实践。

286

2026.06.03

TypeScript Node.js 全栈工程化与Monorepo架构实践
TypeScript Node.js 全栈工程化与Monorepo架构实践

本专题围绕 TypeScript 在 Node.js 全栈开发中的工程化实践展开,系统讲解 Monorepo 架构设计、包管理策略、模块复用机制以及服务端与前端统一类型系统的构建方法。通过真实项目案例,帮助开发者提升大型全栈项目的可维护性与协作效率。

658

2026.06.16

string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

6799

2023.08.02

c语言const用法
c语言const用法

const是关键字,可以用于声明常量、函数参数中的const修饰符、const修饰函数返回值、const修饰指针。详细介绍:1、声明常量,const关键字可用于声明常量,常量的值在程序运行期间不可修改,常量可以是基本数据类型,如整数、浮点数、字符等,也可是自定义的数据类型;2、函数参数中的const修饰符,const关键字可用于函数的参数中,表示该参数在函数内部不可修改等等。

2298

2023.09.20

c语言union的用法
c语言union的用法

c语言union的用法是一种特殊的数据类型,它允许在相同的内存位置存储不同的数据类型,union的使用可以帮助我们节省内存空间,并且可以方便地在不同的数据类型之间进行转换。使用union时需要注意对应的成员是有效的,并且只能同时访问一个成员。本专题为大家提供union相关的文章、下载、课程内容,供大家免费下载体验。

769

2023.09.27

Vite 项目如何打包和部署
Vite 项目如何打包和部署

Vite项目打包部署专题汇集生产构建、产物预览、输出目录、资源路径、Nginx与GitHub Pages部署及常见白屏和路由404问题的已审核文章,帮助读者完成上线流程。

40

2026.10.28

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
WebStorm 官方调试文档
WebStorm 官方调试文档

共0课时 | 0人学习

TypeScript 教程
TypeScript 教程

共19课时 | 6.7万人学习

TypeScript——十天技能课堂
TypeScript——十天技能课堂

共21课时 | 1.7万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn