Skip to Content
拓展@mtpc/adapter-drizzle

@mtpc/adapter-drizzle 使用指南

目录

  1. 概述
  2. 安装与配置
  3. 核心 API 详解
  4. 快速开始
  5. 高级功能
  6. 最佳实践
  7. 常见问题
  8. 架构图示

1. 概述

1.1 包简介

@mtpc/adapter-drizzle 是 MTPC (Multi-Tenant Permission Core) 框架的 Drizzle ORM 数据访问层适配器,提供将多租户权限核心与 Drizzle ORM 无缝集成的解决方案。

1.2 核心功能

  • 自动租户隔离:确保数据按租户正确隔离,避免跨租户访问
  • Repository 模式:提供统一的数据访问接口
  • Schema 生成:从资源定义自动生成 Drizzle 表结构
  • CRUD 操作:完整的 CRUD 操作支持
  • 钩子集成:与 MTPC Core 钩子系统深度集成
  • 迁移管理:数据库 schema 版本控制
  • 查询构建器:支持链式调用构建复杂查询

1.3 适用场景

  • 基于 Drizzle ORM 的多租户应用
  • 需要自动生成数据库 Schema 的场景
  • 采用 Repository 模式设计的数据访问层
  • 需要数据库迁移管理的应用
  • 复杂查询构建需求
  • 多租户数据隔离需求

2. 安装与配置

2.1 安装依赖

pnpm add @mtpc/adapter-drizzle @mtpc/core drizzle-orm postgres

2.2 依赖要求

依赖包版本要求说明
@mtpc/coreworkspace:*MTPC 核心包
drizzle-orm^0.30.0Drizzle ORM 库
postgres^3.4.0PostgreSQL 驱动

2.3 数据库配置

import { createConnection } from '@mtpc/adapter-drizzle'; // 方式一:使用配置对象 const { db, client } = createConnection({ connectionString: 'postgres://user:password@localhost:5432/mydb', maxConnections: 10, idleTimeout: 20, ssl: false, }); // 方式二:从环境变量读取 const { db, client } = createConnectionFromEnv();

DatabaseConfig 选项

属性类型默认值说明
connectionStringstring-数据库连接字符串(必填)
maxConnectionsnumber10最大连接数
idleTimeoutnumber20空闲超时(秒)
sslboolean | objectfalseSSL 配置

3. 核心 API 详解

3.1 数据库连接

createConnection(config: DatabaseConfig)

创建 PostgreSQL 数据库连接。

import { createConnection } from '@mtpc/adapter-drizzle'; const { db, client } = createConnection({ connectionString: process.env.DATABASE_URL, maxConnections: 10, idleTimeout: 20, ssl: process.env.NODE_ENV === 'production', });

createConnectionFromEnv()

从环境变量创建连接,自动读取 DATABASE_URL

import { createConnectionFromEnv } from '@mtpc/adapter-drizzle'; // 需要设置环境变量:DATABASE_URL=postgres://... const { db, client } = createConnectionFromEnv();

ConnectionPool

连接池管理器。

import { createConnectionPool, closeConnection } from '@mtpc/adapter-drizzle'; const pool = createConnectionPool({ connectionString: process.env.DATABASE_URL, }); // 获取数据库实例 const db = pool.getDb(); // 健康检查 const isHealthy = await pool.healthCheck(); // 关闭连接池 await pool.close();

3.2 Schema 生成

generateTable(resource, options)

从资源定义生成单张表。

import { generateTable } from '@mtpc/adapter-drizzle'; import { defineResource } from '@mtpc/core'; import { z } from 'zod'; const userResource = defineResource({ name: 'user', schema: z.object({ id: z.string().uuid(), name: z.string().min(2), email: z.string().email(), }), features: { create: true, read: true, update: true, delete: true }, }); // 生成表结构 const userTable = generateTable(userResource, { tenantColumn: 'tenant_id', timestamps: true, softDelete: false, auditFields: true, });

SchemaGenerationOptions

属性类型默认值说明
tenantColumnstring’tenant_id’租户列名
timestampsbooleantrue添加时间戳
softDeletebooleanfalse添加软删除字段
auditFieldsbooleanfalse添加审计字段

generateAllTables(resources, options)

批量生成所有表的 Schema。

import { generateAllTables } from '@mtpc/adapter-drizzle'; const tables = generateAllTables([userResource, productResource, orderResource], { timestamps: true, auditFields: true, }); console.log(tables); // { user: PgTable, product: PgTable, order: PgTable }

3.3 Repository 工厂

createRepositoryFactory(db, tables)

创建 Repository 工厂实例。

import { createRepositoryFactory } from '@mtpc/adapter-drizzle'; import { generateAllTables } from '@mtpc/adapter-drizzle'; // 生成表结构 const tables = generateAllTables([userResource, productResource]); // 创建工厂 const factory = createRepositoryFactory(db, tables); // 获取用户 Repository const userRepo = factory.getRepository('user'); // 获取产品 Repository(支持驼峰或蛇形命名) const productRepo = factory.getRepository('product'); const orderRepo = factory.getRepository('order'); // 或 'order'

RepositoryFactory 方法

方法参数返回值说明
getRepository<T>(resourceName)资源名称TenantRepository<T>获取指定资源的仓储实例
registerTable(name, table)表名、表定义void注册表定义
getTableNames()-string[]获取所有表名
clearCache()-void清空缓存

3.4 CRUD Repository

BaseRepository 方法

interface Repository<T> { findById(ctx: MTPCContext, id: string): Promise<T | null>; findMany(ctx: MTPCContext, options?: QueryOptions): Promise<PaginatedResult<T>>; create(ctx: MTPCContext, data: Partial<T>): Promise<T>; update(ctx: MTPCContext, id: string, data: Partial<T>): Promise<T | null>; delete(ctx: MTPCContext, id: string): Promise<boolean>; count(ctx: MTPCContext, options?: QueryOptions): Promise<number>; }

TenantRepository 扩展方法

class TenantRepository<T> extends BaseRepository<T> { findAllForTenant(ctx: MTPCContext): Promise<T[]>; createMany(ctx: MTPCContext, items: Partial<T>[]): Promise<T[]>; updateMany(ctx: MTPCContext, ids: string[], data: Partial<T>): Promise<number>; deleteMany(ctx: MTPCContext, ids: string[]): Promise<number>; softDelete(ctx: MTPCContext, id: string): Promise<boolean>; softDeleteMany(ctx: MTPCContext, ids: string[]): Promise<number>; restore(ctx: MTPCContext, id: string): Promise<T | null>; findByIdIncludingDeleted(ctx: MTPCContext, id: string): Promise<T | null>; findDeleted(ctx: MTPCContext, options?: QueryOptions): Promise<PaginatedResult<T>>; }

RepositoryOptions

interface RepositoryOptions { /** 租户列名,默认 "tenantId" */ tenantColumn?: string; }

3.5 查询构建器

createQueryBuilder(db, table, ctx)

创建查询构建器实例。

import { createQueryBuilder } from '@mtpc/adapter-drizzle'; const users = await createQueryBuilder(db, userTable, ctx) .whereEquals('status', 'active') .whereLike('name', 'John%') .whereIn('role', ['admin', 'editor']) .orderBy('createdAt', 'desc') .limit(10) .offset(0) .getMany(); const count = await createQueryBuilder(db, userTable, ctx) .whereEquals('status', 'active') .count(); const exists = await createQueryBuilder(db, userTable, ctx) .whereEquals('email', 'test@example.com') .exists();

QueryBuilder 方法

方法参数返回值说明
where(field, operator, value)字段名、运算符、值this添加条件
whereEquals(field, value)字段名、值this等于条件
whereIn(field, values)字段名、值数组thisIN 条件
whereLike(field, pattern)字段名、模式thisLIKE 条件
whereNull(field)字段名thisIS NULL
whereNotNull(field)字段名thisIS NOT NULL
whereBetween(field, min, max)字段名、最小值、最大值thisBETWEEN
orWhere(conditions)条件数组thisOR 条件
orderBy(field, direction)字段名、方向this排序
limit(value)数量this限制数量
offset(value)偏移量this偏移量
select(...columns)列名数组this选择列(未完成)
withDeleted()-this包含已删除
getMany()-Promise<T[]>获取所有
getOne()-Promise<T | null>获取单个
count()-Promise<number>统计数量
exists()-Promise<boolean>检查存在

支持的运算符eqneqgtgteltlteinlikeisNullisNotNull

3.6 CRUD 处理器

createDrizzleHandlerFactory(db, tables)

创建 CRUD 处理器工厂。

import { createDrizzleHandlerFactory } from '@mtpc/adapter-drizzle'; const handlerFactory = createDrizzleHandlerFactory(db, tables); // 获取用于 Hono 的工厂函数 const createHandler = handlerFactory.getHandlerFactoryFn();

DrizzleCRUDHandler 方法

class DrizzleCRUDHandler<T> implements CRUDHandler<T> { list(ctx: MTPCContext, options?: QueryOptions): Promise<PaginatedResult<T>>; create(ctx: MTPCContext, data: Partial<T>): Promise<T>; read(ctx: MTPCContext, id: string): Promise<T | null>; update(ctx: MTPCContext, id: string, data: Partial<T>): Promise<T | null>; delete(ctx: MTPCContext, id: string): Promise<boolean>; getRepository(): TenantRepository<T>; }

3.7 迁移管理

createMigrationRunner(db, options)

创建迁移运行器。

import { createMigrationRunner, createSystemTablesMigration } from '@mtpc/adapter-drizzle'; const runner = createMigrationRunner(db, { tableName: 'mtpc_migrations' }); // 注册系统表迁移 runner.register(createSystemTablesMigration()); // 执行所有待执行迁移 await runner.migrate(); // 回滚最后一次迁移 await runner.rollback(); // 回滚所有迁移 await runner.reset();

MigrationRunner 方法

方法参数返回值说明
register(migration)迁移定义this注册单个迁移
registerMany(migrations)迁移数组this批量注册
init()-Promise<void>初始化迁移表
migrate()-Promise<string[]>执行迁移
rollback()-Promise<string | null>回滚迁移
reset()-Promise<void>重置所有

4. 快速开始

4.1 完整示例

import { createMTPC } from '@mtpc/core'; import { defineResource } from '@mtpc/core'; import { z } from 'zod'; import { createConnectionFromEnv } from '@mtpc/adapter-drizzle'; import { createRepositoryFactory } from '@mtpc/adapter-drizzle'; import { generateAllTables } from '@mtpc/adapter-drizzle'; // 1. 定义资源 const userResource = defineResource({ name: 'user', schema: z.object({ id: z.string().uuid(), name: z.string().min(2), email: z.string().email(), role: z.enum(['admin', 'user', 'guest']), }), features: { create: true, read: true, update: true, delete: true }, }); // 2. 创建 MTPC 实例 const mtpc = createMTPC(); mtpc.registerResource(userResource); await mtpc.init(); // 3. 创建数据库连接 const { db } = createConnectionFromEnv(); // 4. 生成并注册表 const tables = generateAllTables([userResource], { timestamps: true, auditFields: true, }); // 5. 创建 Repository 工厂 const factory = createRepositoryFactory(db, tables); // 6. 获取 Repository const userRepo = factory.getRepository('user'); // 7. 使用 Repository(需要租户上下文) import { createTenantContext } from '@mtpc/core'; const tenant = createTenantContext('tenant-001'); const subject = { id: 'user-001', type: 'user' as const }; const ctx = { tenant, subject, request: {} }; // 创建用户 const user = await userRepo.create(ctx, { id: '123e4567-e89b-12d3-a456-426614174000', name: '张三', email: 'zhangsan@example.com', role: 'admin', }); // 查询用户 const foundUser = await userRepo.findById(ctx, '123e4567-e89b-12d3-a456-426614174000'); // 列表查询 const { data: users, total } = await userRepo.findMany(ctx, { pagination: { page: 1, pageSize: 10 }, sort: [{ field: 'createdAt', direction: 'desc' }], }); // 更新用户 const updated = await userRepo.update(ctx, '123e4567-e89b-12d3-a456-426614174000', { name: '李四', }); // 删除用户 await userRepo.delete(ctx, '123e4567-e89b-12d3-a456-426614174000');

4.2 与 Hono 集成

import { createMTPCApp } from '@mtpc/adapter-hono'; import { createDrizzleHandlerFactory } from '@mtpc/adapter-drizzle'; const handlerFactory = createDrizzleHandlerFactory(db, tables); const app = createMTPCApp(mtpc, { prefix: '/api', handlerFactory: handlerFactory.getHandlerFactoryFn(), }); app.fire();

5. 高级功能

5.1 批量操作

// 批量创建 const users = await userRepo.createMany(ctx, [ { name: '用户1', email: 'user1@example.com' }, { name: '用户2', email: 'user2@example.com' }, { name: '用户3', email: 'user3@example.com' }, ]); // 批量更新 const updatedCount = await userRepo.updateMany(ctx, ['id1', 'id2', 'id3'], { status: 'inactive', }); // 批量删除 const deletedCount = await userRepo.deleteMany(ctx, ['id1', 'id2']);

5.2 软删除

// 软删除 await userRepo.softDelete(ctx, 'user-id'); // 恢复 await userRepo.restore(ctx, 'user-id'); // 查询已删除记录 const deleted = await userRepo.findDeleted(ctx, { pagination: { page: 1, pageSize: 10 }, }); // 包含已删除的查询 const user = await userRepo.findByIdIncludingDeleted(ctx, 'user-id');

5.3 自定义 Repository

import { BaseRepository, type RepositoryOptions } from '@mtpc/adapter-drizzle/repository'; class CustomUserRepository extends BaseRepository<User> { constructor(db, table, tableName) { super(db, table, tableName, { tenantColumn: 'tenant_id' }); } async findByEmail(email: string): Promise<User | null> { return this.findOne(ctx, [{ field: 'email', operator: 'eq', value: email }]); } async findByRole(role: string): Promise<User[]> { return this.findMany(ctx, { filters: [{ field: 'role', operator: 'eq', value: role }], }); } } // 使用自定义 Repository const customRepo = new CustomUserRepository(db, userTable, 'users'); const user = await customRepo.findByEmail('test@example.com');

5.4 复杂查询构建

import { createQueryBuilder } from '@mtpc/adapter-drizzle'; const results = await createQueryBuilder(db, userTable, ctx) .whereEquals('status', 'active') .whereIn('role', ['admin', 'manager']) .whereLike('name', '%张%') .whereBetween('createdAt', new Date('2024-01-01'), new Date('2024-12-31')) .orderBy('createdAt', 'desc') .limit(20) .getMany();

5.5 自定义 Schema 生成

import { generateTable } from '@mtpc/adapter-drizzle'; const customTable = generateTable(productResource, { tenantColumn: 'company_id', // 自定义租户列名 timestamps: true, // 包含时间戳 softDelete: true, // 包含软删除 auditFields: true, // 包含审计字段 });

5.6 迁移管理

import { createMigrationRunner, createSystemTablesMigration } from '@mtpc/adapter-drizzle'; // 创建迁移运行器 const runner = createMigrationRunner(db, { tableName: 'my_migrations' }); // 注册系统表迁移 runner.register(createSystemTablesMigration()); // 自定义迁移 runner.register({ id: '20240101_add_user_fields', name: 'Add user profile fields', async up(db) { await db.execute(sql.raw(` ALTER TABLE users ADD COLUMN phone VARCHAR(20); ALTER TABLE users ADD COLUMN avatar TEXT; `)); }, async down(db) { await db.execute(sql.raw(` ALTER TABLE users DROP COLUMN phone; ALTER TABLE users DROP COLUMN avatar; `)); }, }); // 执行迁移 await runner.migrate(); // 检查迁移状态 const pending = await runner.getPending(); const executed = await runner.getExecuted();

6. 最佳实践

6.1 项目结构

src/ ├── db/ │ ├── index.ts # 数据库连接和初始化 │ ├── schema.ts # 表结构定义 │ └── migrations/ # 迁移文件 ├── repositories/ │ ├── user.repository.ts │ ├── product.repository.ts │ └── index.ts └── resources/ ├── user.resource.ts ├── product.resource.ts └── index.ts

6.2 Repository 封装

// repositories/user.repository.ts import { createRepositoryFactory } from '@mtpc/adapter-drizzle'; import { userTable } from '../db/schema'; export class UserRepository { constructor(private factory: ReturnType<typeof createRepositoryFactory>) {} get repo() { return this.factory.getRepository('user'); } async findByEmail(email: string) { // 自定义查询逻辑 } async findActiveUsers() { // 自定义查询逻辑 } }

6.3 错误处理

try { const user = await userRepo.findById(ctx, userId); if (!user) { throw new Error('用户不存在'); } // ... } catch (error) { console.error('用户操作失败:', error); throw error; }

6.4 事务支持

await db.transaction(async (tx) => { // 在事务中执行操作 await tx.insert(userTable).values({ ... }); await tx.insert(orderTable).values({ ... }); // 如果发生错误,事务会自动回滚 });

6.5 性能优化

// 1. 使用批量操作 await userRepo.createMany(ctx, users); // 比循环 create 更高效 // 2. 合理使用索引 // Schema 生成器会自动为 tenant_id 和 created_at 添加索引 // 3. 分页查询 const { data, total } = await userRepo.findMany(ctx, { pagination: { page: 1, pageSize: 20 }, // 限制每页数量 }); // 4. 只查询需要的字段(select 方法待完善) // const users = await query.select('id', 'name', 'email').getMany();

7. 常见问题

Q1: 如何自定义租户列名?

// 在 Schema 生成时 const table = generateTable(resource, { tenantColumn: 'company_id', }); // 在 Repository 中 const repo = new BaseRepository(db, table, tableName, { tenantColumn: 'company_id', });

Q2: 软删除如何工作?

软删除通过设置 deleted_atdeleted_by 字段实现:

// 启用软删除 const table = generateTable(resource, { softDelete: true }); // 软删除记录 await userRepo.softDelete(ctx, userId); // 默认查询会排除已删除记录 const activeUsers = await userRepo.findMany(ctx); // 需要查询已删除记录时 const deletedUsers = await userRepo.findDeleted(ctx);

Q3: 如何处理事务?

// 单个 Repository 操作已在内部处理 await userRepo.create(ctx, userData); // 跨表操作使用事务 await db.transaction(async (tx) => { await tx.insert(orderTable).values(orderData); await tx.update(userTable).set({ balance: newBalance }).where(eq(userTable.id, userId)); });

Q4: 如何添加自定义迁移?

runner.register({ id: 'unique_migration_id', name: 'Migration description', async up(db) { // 升级逻辑 }, async down(db) { // 回滚逻辑 }, });

Q5: 如何与现有数据库集成?

如果已有数据库表,可以手动创建 Drizzle 表定义:

import { pgTable, uuid, varchar, timestamp } from 'drizzle-orm/pg-core'; export const myExistingTable = pgTable('my_existing_table', { id: uuid('id').primaryKey().defaultRandom(), name: varchar('name', { length: 255 }).notNull(), createdAt: timestamp('created_at').defaultNow().notNull(), }); // 直接使用现有表 const factory = createRepositoryFactory(db, { myExistingTable });

Q6: 查询构建器为什么不返回指定字段?

select 方法当前标记为 TODO,尚未实现。如需只查询特定字段,可以使用 Drizzle 原生查询:

import { eq, select } from 'drizzle-orm'; const users = await db .select({ id: userTable.id, name: userTable.name }) .from(userTable) .where(eq(userTable.tenantId, ctx.tenant.id)) .limit(10);

8. 架构图示

8.1 数据流图

8.2 租户隔离流程

8.3 Repository 继承结构


附录

A. 类型参考

// 核心类型 type MTPCContext = { tenant: { id: string; status?: string; metadata?: Record<string, unknown> }; subject: { id: string; type: string; attributes?: Record<string, unknown> }; request: { id?: string; timestamp?: Date; ip?: string; path?: string; method?: string }; }; type PaginatedResult<T> = { data: T[]; total: number; page: number; pageSize: number; totalPages: number; hasNext: boolean; hasPrev: boolean; }; type QueryOptions = { pagination?: { page?: number; pageSize?: number }; sort?: SortOptions[]; filters?: FilterCondition[]; }; type FilterCondition = { field: string; operator: 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte' | 'in' | 'contains' | 'startsWith' | 'endsWith' | 'isNull' | 'isNotNull'; value: unknown; }; type SortOptions = { field: string; direction: 'asc' | 'desc'; };

B. 错误处理

// 常见错误类型 class TableNotFoundError extends Error { constructor(resourceName: string) { super(`Table not found for resource: ${resourceName}`); this.name = 'TableNotFoundError'; } } class ColumnNotFoundError extends Error { constructor(columnName: string) { super(`Column not found: ${columnName}`); this.name = 'ColumnNotFoundError'; } }

文档版本: 1.0.0
最后更新: 2024-12-27


继续学习: 开发指南

Last updated on