如何在TypeScript中为Kysely正确构建匹配类型键的数组?
Kysely 动态传入Select键的类型兼容方案
问题背景
我正在使用Vercel推荐的Kysely JS SQL构建器(文档/社区资源较少),它是全类型化的SQL构建器,通过Schema构建db对象后,查询时可识别表名和属性。
初始化代码如下:
import 'dotenv/config' import { createKysely } from '@vercel/postgres-kysely' import { DB } from 'kysely-codegen' export const db = createKysely<DB>() export { sql } from 'kysely'
DB接口由PostgreSQL Schema直接生成,示例片段:
export interface MyTable { id: string foo: string bar: boolean } export interface DB { my_table: MyTable }
查询my_table时,静态传入键数组(如['foo', 'id'])可正常运行,但从JSON动态获取键时:
// 模拟未知输入,类似浏览器JSON API请求 const json = JSON.parse(fs.readFileSync('test.json')) const selectKeys = Object.keys(json) const record = await db .selectFrom('my_table') .select(selectKeys) .executeTakeFirst()
会触发类型错误:
Argument of type 'string[]' is not assignable to parameter of type 'SelectArg<DB, "my_table", SelectExpression<DB, "my_table">>'
目前通过类型断言或提前定义键数组过滤解决,但存在重复定义问题,需更优方案。
解决方案
方案1:类型守卫校验动态键
定义类型守卫函数,同时做运行时校验和类型推导,避免纯断言的不安全,且字段列表仅需维护一次:
import { MyTable } from './path-to-db-types' type MyTableKey = keyof MyTable function isMyTableKey(key: string): key is MyTableKey { return ['id', 'foo', 'bar'].includes(key) } // 使用时过滤并推导类型 const json = JSON.parse(fs.readFileSync('test.json')) const selectKeys = Object.keys(json).filter(isMyTableKey) const record = await db .selectFrom('my_table') .select(selectKeys) .executeTakeFirst()
方案2:利用Kysely的sql标记动态字段
适合字段来源可信的场景,直接将字段转为Kysely可识别的SQL表达式,同时避免SQL注入风险:
const json = JSON.parse(fs.readFileSync('test.json')) const selectKeys = Object.keys(json) const record = await db .selectFrom('my_table') .select(selectKeys.map(key => sql`${sql.raw(key)}`)) .executeTakeFirst()
方案3:基于DB类型自动校验字段列表
用TypeScript的satisfies关键字,让运行时字段数组严格匹配类型定义,Schema变更时会自动提示更新:
import { DB } from './path-to-db-types' type MyTableKey = keyof DB['my_table'] // TypeScript会校验数组元素是否符合MyTableKey类型 const MY_TABLE_KEYS = ['id', 'foo', 'bar'] as const satisfies readonly MyTableKey[] // 过滤动态键 const json = JSON.parse(fs.readFileSync('test.json')) const selectKeys = Object.keys(json).filter(key => MY_TABLE_KEYS.includes(key as MyTableKey)) as MyTableKey[] const record = await db .selectFrom('my_table') .select(selectKeys) .executeTakeFirst()
内容的提问来源于stack exchange,提问作者Lance Pollard
相关产品推荐
相关产品推荐

