npm package: @gulibs/tegg-prisma
Description: Prisma plugin for Egg.js 4.x
Author: Your Name
Latest version: 0.1.6

# @gulibs/tegg-prisma

适用于 Egg.js 4.x 的 Prisma 插件，提供完整的 TypeScript 支持。

## 系统要求

- Node.js >= 22.18.0
- Egg.js >= 4.1.0-beta.35
- Prisma >= 6.0.0（完全支持 Prisma 7.x）

## 特性

- ✅ **自动类型推导** - 零配置 TypeScript 支持！（v0.1.3+）
- ✅ 完整的 Prisma schema IntelliSense
- ✅ **Driver Adapter 支持** - Prisma 7.x（v0.1.3+）
- ✅ 单数据库和多数据库连接
- ✅ 自动连接和优雅断开
- ✅ 支持自定义 Prisma Client
- ✅ Agent 进程支持（可选）
- ✅ 通过 `app.teggPrisma` 和 `ctx.prisma` 便捷访问
- ✅ 查询性能监控
- ✅ 健康检查 API
- ✅ 带自动重试的事务
- ✅ 增强的错误处理

## 安装

```bash
npm i @gulibs/tegg-prisma @prisma/client
npm i -D prisma
```

## 快速开始

### 1. 启用插件

```typescript
// config/plugin.ts
import prismaPlugin from '@gulibs/tegg-prisma';

export default {
  ...prismaPlugin(),
};
```

### 2. 设置 Prisma

```bash
# 初始化 Prisma
npx prisma init

# 在 prisma/schema.prisma 中定义模型
# 然后生成 Prisma Client
npx prisma generate
```

### 3. 启动应用

```bash
npm run dev
```

**就这样！** TypeScript 会自动推导 PrismaClient 类型 - 无需任何配置。

### 4. 配置

```typescript
// config/config.default.ts
export default {
  teggPrisma: {
    client: {
      // 可选：覆盖数据库 URL
      // DATABASE_URL 从 .env 或 schema.prisma 中配置

      // 可选：配置日志
      log: ['query', 'error', 'warn'],

      // 可选：错误格式
      errorFormat: 'colorless',
    }
  }
};
```

### 5. 使用

```typescript
// app/controller/user.ts
import { Controller, HTTPController, HTTPMethod, HTTPContext } from 'egg';

@HTTPController()
export class UserController {
  @HTTPMethod({
    path: '/users',
    method: 'GET',
  })
  async list(@HTTPContext() ctx: Context) {
    // 通过 ctx.prisma 访问 Prisma Client
    const users = await ctx.prisma.user.findMany();
    ctx.body = users;
  }

  @HTTPMethod({
    path: '/users/:id',
    method: 'GET',
  })
  async show(@HTTPContext() ctx: Context) {
    const { id } = ctx.params;
    const user = await ctx.prisma.user.findUnique({
      where: { id },
    });
    ctx.body = user;
  }

  @HTTPMethod({
    path: '/users',
    method: 'POST',
  })
  async create(@HTTPContext() ctx: Context) {
    const user = await ctx.prisma.user.create({
      data: ctx.request.body,
    });
    ctx.body = user;
  }
}
```

## 类型安全 - 自动类型推导 🎯

### v0.1.3 新特性：零配置类型推导！

插件现在**自动推导** PrismaClient 类型 - 无需手动类型断言或配置！

```typescript
// ✅ 直接使用 - IntelliSense 自动生效！
const users = await ctx.prisma.user.findMany({
  include: { posts: true }  // ✅ 自动补全生效！
});

const user = await ctx.prisma.user.create({
  data: {
    name: 'John',
    email: 'john@example.com',
    posts: {
      create: { title: '我的第一篇文章' }
    }
  }
});
```

**工作原理**：
- 插件使用 TypeScript 条件类型自动从 `@prisma/client` 导入和推导 `PrismaClient` 类型
- 当你运行 `npx prisma generate` 时，schema 模型会被完全类型化
- 插件的 `EggPrisma` 类型自动解析为你生成的 `PrismaClient` 实例类型
- **零配置** - 只需安装和使用！

**结果**：在项目的任何地方都有完美的 IntelliSense！

---

## 数据库连接方式

Prisma 7.x 支持两种数据库连接配置方式：

### 方式 1：环境变量（推荐用于大多数情况）

**适用于**：单数据库，简单配置

在 `.env` 文件中配置 `DATABASE_URL`：

```bash
# .env
DATABASE_URL="postgresql://user:pass@localhost:5432/mydb?connection_limit=10&pool_timeout=30"
```

```typescript
// config/config.default.ts
export default {
  teggPrisma: {
    client: {
      // DATABASE_URL 自动从环境变量读取
      log: ['query', 'error', 'warn'],
    }
  }
};
```

**优点**：简单，符合 12-factor app 原则，兼容 Prisma CLI

### 方式 2：Driver Adapter（Prisma 7.x 新增）

**适用于**：动态数据库配置，多数据库，运行时连接变更

安装 adapter：

```bash
# PostgreSQL
npm i @prisma/adapter-pg pg

# MySQL
npm i @prisma/adapter-mysql2 mysql2

# 其他 adapter：@prisma/adapter-planetscale, @prisma/adapter-neon 等
```

在配置中使用 adapter：

```typescript
// config/config.default.ts
import { PrismaPg } from '@prisma/adapter-pg';

// 创建带有 connectionString 的 adapter
const adapter = new PrismaPg({
  connectionString: process.env.DATABASE_URL
});

export default {
  teggPrisma: {
    client: {
      adapter,  // 将 adapter 传递给插件
      log: ['query', 'error', 'warn'],
    }
  }
};
```

**优点**：
- ✅ 运行时配置（可以动态更改连接）
- ✅ 支持边缘运行时（Cloudflare Workers、Vercel Edge 等）
- ✅ 高级连接池控制
- ✅ 多数据库支持不同驱动

**参考**：[Prisma Driver Adapters 文档](https://www.prisma.io/docs/orm/overview/databases/database-drivers)

---

## 配置

### 单数据库

```typescript
export default {
  teggPrisma: {
    client: {
      // DATABASE_URL 从 .env 或 schema.prisma 中配置
      log: ['query', 'error', 'warn'],

      // 可选：事务选项
      transactionOptions: {
        maxWait: 5000,
        timeout: 10000,
        isolationLevel: 'ReadCommitted',
      },

      // 可选：查询监控
      queryMonitor: {
        enabled: true,
        slowQueryThreshold: 1000,  // 1秒
        logAllQueries: process.env.NODE_ENV === 'development',
      },
    }
  }
};
```

### 多数据库

```typescript
export default {
  teggPrisma: {
    clients: {
      db1: {
        // 使用自定义 PrismaClient（从 schema-db1.prisma 生成）
        log: ['error'],
      },
      db2: {
        // 使用自定义 PrismaClient（从 schema-db2.prisma 生成）
        log: ['error'],
      },
    }
  }
};
```

访问多个数据库：

```typescript
// 获取特定客户端
const db1 = app.teggPrisma.get('db1');
const users = await db1.user.findMany();

// 或通过 ctx
const db2 = ctx.prisma.get('db2');
const posts = await db2.post.findMany();
```

### 自定义 Prisma Client

如果有自定义生成的 Prisma Client（例如在 monorepo 中）：

```typescript
import { PrismaClient as CustomPrismaClient } from './prisma/client';

export default {
  teggPrisma: {
    client: {
      PrismaClient: CustomPrismaClient,
      // DATABASE_URL 从 .env 或 schema.prisma 中配置
    }
  }
};
```

### Agent 支持

在 agent 进程中启用 Prisma（用于定时任务等）：

```typescript
export default {
  teggPrisma: {
    agent: true, // 在 agent 中启用
    client: {
      // DATABASE_URL 从 .env 或 schema.prisma 中配置
    }
  }
};
```

## API

### app.teggPrisma

在应用中访问 Prisma Client：

```typescript
// 单客户端
const users = await app.teggPrisma.user.findMany();

// 多客户端
const db1 = app.teggPrisma.get('db1');
const users = await db1.user.findMany();

// 或使用别名
const db2 = app.teggPrismas.get('db2');
```

### ctx.prisma

在上下文中访问 Prisma Client：

```typescript
async show(ctx: Context) {
  const user = await ctx.prisma.user.findUnique({
    where: { id: ctx.params.id },
  });
  ctx.body = user;
}
```

### CLIENT_NAME_SYMBOL

获取客户端名称：

```typescript
import { CLIENT_NAME_SYMBOL } from '@gulibs/tegg-prisma';

const clientName = Reflect.get(ctx.prisma, CLIENT_NAME_SYMBOL);
console.log(clientName); // 'default' 或客户端名称
```

### 健康检查 API

检查数据库连接健康状态：

```typescript
// 单客户端
const healthy = await app.checkPrismaHealth();
if (!healthy) {
  console.error('数据库连接不健康');
}

// 多客户端
const db1Healthy = await app.checkPrismaHealth('db1');
const db2Healthy = await app.checkPrismaHealth('db2');

// 获取所有客户端健康状态
const status = await app.getPrismaHealthStatus();
console.log(status); // { default: true, db1: true, db2: false }
```

健康检查端点示例：

```typescript
@HTTPMethod({ path: '/health', method: 'GET' })
async healthCheck(@HTTPContext() ctx: Context) {
  const dbStatus = await ctx.app.getPrismaHealthStatus();
  const allHealthy = Object.values(dbStatus).every(v => v);

  ctx.body = {
    status: allHealthy ? 'ok' : 'error',
    database: dbStatus,
    timestamp: new Date().toISOString(),
  };

  ctx.status = allHealthy ? 200 : 503;
}
```

## 高级功能

### 1. 查询性能监控

使用 Prisma Client Extensions 监控慢查询并追踪查询性能。

**要求**：Prisma Client 4.16.0 或更高版本

**工作原理**：插件使用 Prisma 的 `$extends` API 拦截和监控所有数据库查询，不会影响性能。

```typescript
// config/config.default.ts
export default {
  teggPrisma: {
    client: {
      // DATABASE_URL 从 .env 或 schema.prisma 中配置
      queryMonitor: {
        enabled: true,
        slowQueryThreshold: 1000,  // 查询超过1秒发出警告
        logAllQueries: process.env.NODE_ENV === 'development',
        onSlowQuery: (info) => {
          // 发送到监控服务
          console.warn('检测到慢查询:', {
            model: info.model,
            action: info.action,
            duration: info.duration,
            timestamp: info.timestamp,
          });
        },
        onQuery: (info) => {
          // 在开发环境追踪所有查询
          console.log(`查询: ${info.model}.${info.action} - ${info.duration}ms`);
        },
      },
    }
  }
};
```

**注意**：查询监控使用 Prisma 4.16.0 引入的 Client Extensions (`$extends`)，它替代了已废弃的 `$use` middleware API。

### 2. 连接池配置

**重要**：在 Prisma 7.x 中，连接池设置必须在 DATABASE_URL 连接字符串中配置：

```bash
# .env - 在连接字符串中配置池设置
DATABASE_URL="postgresql://user:pass@host:5432/db?connection_limit=10&pool_timeout=30"
```

```typescript
export default {
  teggPrisma: {
    client: {
      // DATABASE_URL 从 .env 或 schema.prisma 中配置  // 池设置从 URL 读取
      transactionOptions: {
        maxWait: 5000,        // 事务开始的最大等待时间
        timeout: 10000,       // 事务执行的最大时间
        isolationLevel: 'ReadCommitted',
      },
    }
  }
};
```

**常用连接池参数**：
- `connection_limit` - 最大连接数（PostgreSQL/MySQL）
- `pool_timeout` - 超时时间（秒）
- `connect_timeout` - 连接超时

**参考**：https://www.prisma.io/docs/guides/database/connection-pooling

### 3. 带自动重试的事务

使用事务辅助函数，自动重试逻辑：

```typescript
import { withTransaction } from '@gulibs/tegg-prisma';

async transfer(@HTTPContext() ctx: Context) {
  const result = await withTransaction(
    ctx.prisma,
    async (tx) => {
      const from = await tx.account.update({
        where: { id: ctx.request.body.fromId },
        data: { balance: { decrement: ctx.request.body.amount } },
      });

      if (from.balance < 0) {
        throw new Error('余额不足');
      }

      const to = await tx.account.update({
        where: { id: ctx.request.body.toId },
        data: { balance: { increment: ctx.request.body.amount } },
      });

      return { from, to };
    },
    {
      maxRetries: 3,
      timeout: 10000,
      isolationLevel: 'Serializable',
    }
  );

  ctx.body = result;
}
```

### 4. 增强的错误处理

使用内置的错误处理工具：

```typescript
import { isPrismaError, getPrismaErrorMessage, PrismaErrorCodes } from '@gulibs/tegg-prisma';

async create(@HTTPContext() ctx: Context) {
  try {
    const user = await ctx.prisma.user.create({
      data: ctx.request.body,
    });
    ctx.body = user;
  } catch (error) {
    if (isPrismaError(error)) {
      if (error.code === PrismaErrorCodes.UNIQUE_CONSTRAINT) {
        ctx.status = 400;
        ctx.body = {
          error: '邮箱已存在',
          message: getPrismaErrorMessage(error),
        };
        return;
      }

      if (error.code === PrismaErrorCodes.FOREIGN_KEY_CONSTRAINT) {
        ctx.status = 400;
        ctx.body = { error: '引用的记录不存在' };
        return;
      }
    }
    throw error;
  }
}
```

### 5. 原生 SQL 查询

需要时执行原生 SQL：

```typescript
// 使用 app
const users = await app.prismaRawQuery`
  SELECT * FROM "User" WHERE age > ${18}
`;

// 使用 ctx
const stats = await ctx.prisma.$queryRaw`
  SELECT COUNT(*) as total, AVG(age) as avgAge FROM "User"
`;
```

### 6. Prisma 指标

获取 Prisma Client 指标（如果启用了指标）：

```typescript
const metrics = await app.getPrismaMetrics();
if (metrics) {
  console.log('活跃连接数:', metrics.connections?.active);
  console.log('查询持续时间:', metrics.queries?.duration);
}
```

## 最佳实践

### 1. 事务支持

```typescript
async transfer(ctx: Context) {
  const result = await ctx.prisma.$transaction(async (prisma) => {
    await prisma.account.update({
      where: { id: 1 },
      data: { balance: { decrement: 100 } },
    });

    await prisma.account.update({
      where: { id: 2 },
      data: { balance: { increment: 100 } },
    });

    return { success: true };
  });

  ctx.body = result;
}
```

### 2. 查询优化

```typescript
// ✅ 使用 select 获取特定字段
const users = await ctx.prisma.user.findMany({
  select: { id: true, name: true, email: true },
});

// ❌ 避免获取不必要的数据
const users = await ctx.prisma.user.findMany(); // 获取所有字段

// ✅ 使用分页
const users = await ctx.prisma.user.findMany({
  take: 10,
  skip: (page - 1) * 10,
  orderBy: { createdAt: 'desc' },
});

// ✅ 监控慢查询
// 在配置中启用 queryMonitor 可自动检测 N+1 查询问题
```

### 3. 生产环境部署

```typescript
// config/config.prod.ts
export default {
  teggPrisma: {
    client: {
      // DATABASE_URL 从 .env 或 schema.prisma 中配置  // 在 URL 中包含池设置
      log: ['error'],  // 生产环境只记录错误
      queryMonitor: {
        enabled: true,
        slowQueryThreshold: 2000,  // 生产环境更高的阈值
        onSlowQuery: (info) => {
          // 发送到监控服务（如 Sentry、DataDog）
          monitoring.trackSlowQuery({
            model: info.model,
            action: info.action,
            duration: info.duration,
          });
        },
      },
      transactionOptions: {
        maxWait: 10000,
        timeout: 30000,
        isolationLevel: 'ReadCommitted',
      },
    }
  }
};
```

## 配置选项参考

### 完整配置

```typescript
export default {
  teggPrisma: {
    // 在 app 进程中启用（默认: true）
    app: true,

    // 在 agent 进程中启用（默认: false）
    agent: false,

    // 所有客户端的默认选项
    default: {
      log: ['error', 'warn'],
    },

    // 单客户端配置
    client: {
      // 数据库 URL
      // DATABASE_URL 从 .env 或 schema.prisma 中配置

      // 日志配置
      log: ['query', 'error', 'warn'],
      // 或使用 emit 模式
      log: [
        { emit: 'event', level: 'query' },
        { emit: 'stdout', level: 'error' },
      ],

      // 错误格式
      errorFormat: 'colorless', // 'pretty' | 'colorless' | 'minimal'

      // Driver Adapter（Prisma 7.x）
      // 用于动态数据库配置
      adapter: myAdapter, // driver adapter 实例
      // 示例：
      // import { PrismaPg } from '@prisma/adapter-pg';
      // const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL });

      // 连接池 - 请在 DATABASE_URL 中配置：
      // postgresql://...?connection_limit=10&pool_timeout=30

      // 事务选项
      transactionOptions: {
        maxWait: 5000,
        timeout: 10000,
        isolationLevel: 'ReadCommitted',
      },

      // 查询监控
      queryMonitor: {
        enabled: true,
        slowQueryThreshold: 1000,
        logAllQueries: false,
        onSlowQuery: (info) => {
          console.warn('慢查询:', info);
        },
        onQuery: (info) => {
          console.log('查询:', info);
        },
      },

      // 自定义 Prisma Client
      PrismaClient: CustomPrismaClient,
    },

    // 或多客户端配置
    clients: {
      db1: {
        // 使用自定义 PrismaClient（从 schema-db1.prisma 生成）
        // ... 与单客户端相同的选项
      },
      db2: {
        // 使用自定义 PrismaClient（从 schema-db2.prisma 生成）
        // ... 与单客户端相同的选项
      },
    },
  }
};
```

## 故障排除

### 问题："PrismaClient is unable to run in this browser environment"

确保你没有为浏览器打包 Prisma Client。Prisma 只能在 Node.js 中运行。

### 问题："@prisma/client did not initialize yet"

安装 `@prisma/client` 后运行 `npx prisma generate`。

### 问题：数据库连接问题

检查 `.env` 文件中的 `DATABASE_URL`：

```env
DATABASE_URL="postgresql://user:password@localhost:5432/mydb?connection_limit=10"
```

### 问题：慢查询

启用查询监控以检测慢查询：

```typescript
queryMonitor: {
  enabled: true,
  slowQueryThreshold: 1000,
  logAllQueries: process.env.NODE_ENV === 'development',
}
```

### 问题：连接池耗尽

在 DATABASE_URL 中增加连接限制：

```bash
# 在连接字符串中增加连接限制
DATABASE_URL="postgresql://...?connection_limit=20&pool_timeout=60"
```

## API 参考

### 导出的工具

```typescript
// 错误处理
import {
  isPrismaError,
  getPrismaErrorMessage,
  PrismaErrorCodes,
  getUniqueConstraintField,
} from '@gulibs/tegg-prisma';

// 事务工具
import { withTransaction } from '@gulibs/tegg-prisma';
import type { TransactionOptions } from '@gulibs/tegg-prisma';

// 查询监控类型
import type { QueryMonitorOptions, QueryInfo } from '@gulibs/tegg-prisma';

// 配置类型
import type {
  EggPrismaConfig,
  EggPrismaClientOption
} from '@gulibs/tegg-prisma';

// 客户端名称 symbol
import { CLIENT_NAME_SYMBOL } from '@gulibs/tegg-prisma';
```

### Application 方法

```typescript
// 健康检查
await app.checkPrismaHealth(clientName?: string): Promise<boolean>
await app.getPrismaHealthStatus(): Promise<Record<string, boolean>>

// 原生查询
await app.prismaRawQuery<T>(sql: TemplateStringsArray, ...values: any[]): Promise<T[]>

// 指标
await app.getPrismaMetrics(clientName?: string): Promise<any>
```

### 错误码

```typescript
PrismaErrorCodes.UNIQUE_CONSTRAINT       // P2002
PrismaErrorCodes.FOREIGN_KEY_CONSTRAINT  // P2003
PrismaErrorCodes.RECORD_NOT_FOUND        // P2025
PrismaErrorCodes.RELATION_VIOLATION      // P2014
PrismaErrorCodes.DATABASE_UNREACHABLE    // P1001
PrismaErrorCodes.DATABASE_TIMEOUT        // P1008
PrismaErrorCodes.SERVER_CLOSED           // P1017
```

## 迁移指南

### 从 egg-sequelize 迁移

主要区别：

1. **Schema 定义**：Prisma 使用 `schema.prisma`，而非模型文件
2. **代码生成**：修改 schema 后运行 `npx prisma generate`
3. **类型安全**：Prisma 提供开箱即用的更好类型推断
4. **API**：Prisma 有更现代化的 fluent API
5. **健康检查**：内置健康检查 API
6. **监控**：内置查询性能监控

示例迁移：

```typescript
// Sequelize
const users = await ctx.model.User.findAll({
  where: { age: { $gt: 18 } },
  include: [{ model: ctx.model.Post }],
});

// Prisma
const users = await ctx.prisma.user.findMany({
  where: { age: { gt: 18 } },
  include: { posts: true },
});
```

**迁移到 Prisma 的好处：**
- ✅ 更好的类型安全和自动补全
- ✅ 现代化查询 API
- ✅ 内置迁移工具
- ✅ 查询性能监控
- ✅ 健康检查 API
- ✅ 事务自动重试
- ✅ 更好的错误处理

## 许可证

MIT

## 文档

- [TYPE-SAFETY.md](./TYPE-SAFETY.md) - 类型安全最佳实践
- [WHY-ONLY-AUGMENT-EGG.md](./WHY-ONLY-AUGMENT-EGG.md) - TypeScript 模块增强详解

## 链接

- [Prisma 文档](https://www.prisma.io/docs)
- [Egg.js 文档](https://eggjs.org)
- [GitHub 仓库](https://github.com/your-org/tegg-prisma)

## 更新日志

查看 [CHANGELOG.md](./CHANGELOG.md) 了解版本历史和迁移指南。