Drizzle:带分页与总计数的通用查询函数如何实现类型安全?
分页查询的类型安全实现方案
一、为查询函数添加泛型约束
如果要保留query函数的复用方式,可以通过泛型结合ORM的内置类型来恢复类型安全。以Drizzle ORM为例:
import type { SelectQueryBuilder, Selectable } from 'drizzle-orm'; import { hotels, cities, eq, count } from './schema'; // 定义关联查询的结果类型 type HotelCityRecord = typeof hotels.$inferSelect & { cityId: typeof cities.id.$type }; // 泛型函数:F约束传入的字段结构,R指定查询结果类型 function query<F extends Record<string, any>, R = Selectable<F>>(fields: F): SelectQueryBuilder<typeof db, R> { return db .select(fields) .from(hotels) .innerJoin(cities, eq(cities.id, hotels.city_id)) .where(eq(hotels.locale, 'cs')) as SelectQueryBuilder<typeof db, R>; } // 计数查询,自动推断返回类型为Array<{ count: number }> const total = await query({ count: count() }); // 数据查询,指定泛型后自动获得类型提示 const rows = await query<HotelCityRecord>({ id: hotels.id, cityId: cities.id, name: hotels.name, }) .orderBy(hotels.id) .limit(limit) .offset(offset);
二、复用基础查询构建器(更简洁)
直接拆分核心逻辑为不带select的基础查询,再分别扩展计数和分页逻辑,完全保留ORM的原生类型支持:
import { hotels, cities, eq, count } from './schema'; // 构建核心查询逻辑(不带select,复用关联、过滤条件) const baseQuery = db .from(hotels) .innerJoin(cities, eq(cities.id, hotels.city_id)) .where(eq(hotels.locale, 'cs')); // 执行计数查询 const total = await baseQuery.select({ count: count() }); // 执行分页数据查询 const rows = await baseQuery .select({ id: hotels.id, cityId: cities.id, name: hotels.name, }) .orderBy(hotels.id) .limit(limit) .offset(offset);
这种方式无需额外封装函数,代码更直观,且类型安全完全由ORM原生保障,是最推荐的方案。
三、使用ORM原生分页工具
如果使用的ORM支持原生分页(如Drizzle的分页插件、Prisma的findMany),可以直接借助官方工具简化实现:
以Drizzle分页插件为例:
import { paginate } from 'drizzle-orm/pagination'; import { hotels, cities, eq } from './schema'; const baseQuery = db .from(hotels) .innerJoin(cities, eq(cities.id, hotels.city_id)) .where(eq(hotels.locale, 'cs')); // 一次调用同时获取分页数据和总条数 const { data: rows, totalCount } = await paginate(baseQuery, { limit, offset, select: { id: hotels.id, cityId: cities.id, name: hotels.name, }, });
内容的提问来源于stack exchange,提问作者romario333
相关产品推荐
相关产品推荐

