I am using the Kysely SQL builder for JS (as per Vercel's recommendation, although the docs/community is sparse). It is a fully typed SQL builder. You construct a db
object with a schema, and when you do queries it recognizes the table names and attributes (pretty fancy if I do say so myself).
import 'dotenv/config'
import { createKysely } from '@vercel/postgres-kysely'
import { DB } from 'kysely-codegen'
export const db = createKysely<DB>()
export { sql } from 'kysely'
The DB
is generated from the PostgreSQL schema directly, and stored in the kysely-codegen
node_modules folder. It looks like this (short snippet):
export interface MyTable {
id: string
foo: string
bar: boolean
}
export interface DB {
my_table: MyTable
}
Now, my question revolves around using the select
function, which takes an array of keys of MyTable
(when querying my_table
).
const record = await db
.selectFrom('my_table')
.select(['foo', 'id'])
.executeTakeFirst()
That works fine. But it doesn't work when I do this:
// simulate not knowing what the input is
// as if you were making a browser JSON API request
const json = JSON.parse(fs.readFileSync('test.json'))
const selectKeys = Object.keys(json)
const record = await db
.selectFrom('my_table')
.select(selectKeys)
.executeTakeFirst()
I get essentially this error:
Argument of type 'string[]' is not assignable to parameter of type 'SelectArg<DB, "my_table", SelectExpression<DB, "my_table">>'.
I can fix it like this:
const record = await db
.selectFrom('my_table')
.select(selectKeys as Array<keyof MyTable>)
.executeTakeFirst()
I can also guarantee somewhere earlier in the code that the selectKeys
is built with the keys of my_table, by doing something like this:
const MY_TABLE_KEYS: Array<keyof MyTable> = ['id', 'foo', 'bar']
function getKeys(json) {
const keys = []
for (const key in json) {
if (MY_TABLE_KEYS.includes(key)) {
keys.push(key)
}
}
return keys
}
I have basically duplicated/copied the keys from the DB interface into an array, but there are several problems with the getKeys
function, not sure a way around it. A rough TS playground is here demonstrating some of the challenges I'm facing.
The question is, how can I properly type the keys to pass to the .select
method?
You might be looking for a type predicate / type guard. Type predicates are functions that assert/declare that an argument is a specific type if the function returns true. In this case a simple predicate might look like
In the simple example above, I always return true. To make this useful you would wan't to check if the key is actually one of the keys you know about. You can use this example below to get what you're looking for.
You'll notice that you still have some issues with the
record[key] = json[key]
but that's becausejson[key]
may be undefined (because it's a partial);