Guidance to create a generic wrapper over Kysely

I am trying to create a generic authentication wrapper over Kysely. The idea is - You can pass some config to this function, which includes a reference to Kysely instance, a database table name and an array of column names. - The function will return an authenticator instance you can use to perform user lookups during Login. The internals of this function are not important for this discussion. This is what I have so far. You can copy/paste the following code to run it yourself.
import { Pool } from 'pg'
import { AnyColumn, ColumnType, Generated, Kysely, PostgresDialect } from 'kysely'

/**
* Interfaces for the table and the database
*/
interface UserTable {
id: Generated<number>
email: string
username: string
password: string
created_at: ColumnType<Date, string | undefined, never>
}
interface Database {
users: UserTable
}

/**
* PostgreSQL dialect and kysely instance
*/
const dialect = new PostgresDialect({ pool: new Pool(poolConfig) })
const db = new Kysely<Database>({ dialect })

/**
* Authenticator function that need kysely instance
* and an unknown table + columns
*/
function createAuthenticator<
DB,
TB extends keyof DB & string,
Columns extends AnyColumn<DB, TB>
>(config: {
db: Kysely<DB>
table: TB,
uids: Columns[]
}) {
config
.db
.selectFrom(config.table)
// The where clause gives an error
.where(config.uids[0], '=', 'foobar')
}

/**
* Using function to create authenticator
*/
createAuthenticator({
db: db,
table: 'users',
uids: ['email'],
})
import { Pool } from 'pg'
import { AnyColumn, ColumnType, Generated, Kysely, PostgresDialect } from 'kysely'

/**
* Interfaces for the table and the database
*/
interface UserTable {
id: Generated<number>
email: string
username: string
password: string
created_at: ColumnType<Date, string | undefined, never>
}
interface Database {
users: UserTable
}

/**
* PostgreSQL dialect and kysely instance
*/
const dialect = new PostgresDialect({ pool: new Pool(poolConfig) })
const db = new Kysely<Database>({ dialect })

/**
* Authenticator function that need kysely instance
* and an unknown table + columns
*/
function createAuthenticator<
DB,
TB extends keyof DB & string,
Columns extends AnyColumn<DB, TB>
>(config: {
db: Kysely<DB>
table: TB,
uids: Columns[]
}) {
config
.db
.selectFrom(config.table)
// The where clause gives an error
.where(config.uids[0], '=', 'foobar')
}

/**
* Using function to create authenticator
*/
createAuthenticator({
db: db,
table: 'users',
uids: ['email'],
})
The .where(config.uids[0]) method call gives a type error saying, "The Columns are not assignable to the input accepted by the where method". So, I need some guidance on which approach to take for this use case.
K
koskimas162d ago
It's very hard or impossible to write generic wrappers like this for kysely. Kysely's super strict typing doesn't work well with generics. Even if you managed to get this working with some insane type gymnastics, the next version would probably break it.
V
virk162d ago
Ahh okay. Thanks for your reply 🙂
I
Igal162d ago
You'd jump through hoops making this viable, and it'll look pretty ugly and not type-safe under the hood due to how specific kysely is and typescript's limitations - but you could probably make a decent user experience still. and could lock to a specific kysely version.
V
virk147d ago
I have another use-case, lemme ask you guys before I give up on this one. So, here I am trying to create a generic firstOrCreate helper function. This is how the implementation looks like so far.
function firstOrCreate<DB, TB extends keyof DB>(
db: Kysely<DB>,
table: TB & string,
searchAttributes: Readonly<FilterObject<DB, TB>>
) {
db.selectFrom(table).where((eb) => eb.and(searchAttributes))
}
function firstOrCreate<DB, TB extends keyof DB>(
db: Kysely<DB>,
table: TB & string,
searchAttributes: Readonly<FilterObject<DB, TB>>
) {
db.selectFrom(table).where((eb) => eb.and(searchAttributes))
}
I can execute the function with no errors.
firstOrCreate(db, 'users', { name: 'foo' })
firstOrCreate(db, 'users', { name: 'foo' })
However, the eb.and(searchAttributes) method call gives an error --- Is it too much to ask from Kysely internals to work with generic functions like this? Or you think it can work?
V
virk147d ago
Here's the error screenshot
No description
I
Igal147d ago
@virk The error you're seeing is due to a common typescript limitation. You cannot have both generic helpers and type-safety within them when using something specific as a kysely method. DB is anything and TB is just a string in your function's context, so anything you put in eb.and is just too wide and not assignable to the expected types. Your function is still type-safe, autocompletion friendly and produces the same SQL as long you got arguments and return type covered - which is what your consumer cares about. The internals cannot be type-safe - you must escape it with searchAttributes as any - which is what you care about. Using db.selectFrom(table) as SelectQueryBuilder<any, any, {}> can reduce amount of as any downstream.
V
virk147d ago
Hey, thanks for the reply. Cool, just wanted to make sure I am not shooting myself in foot by typecasting to any
Want results from more Discord servers?
Add your server
More Posts
Any way to docutype a Column as `@deprecated`?Hello, We'd like to have columns we specify in out types with the `@deprecated` type identifier in tHelp with CTE queryCan anyone help me transform this SQL to Kysely? https://kyse.link/?p=s&i=Q7JBZIP6xMpYoEsHJSHiError this query cannot be compiled to SQLI am trying to do ``` await trx.executeQuery( sql<any>`ALTER SCHEMA ${currenObject literal may only specify known properties, and 'clientId' does not exist in type 'InsertObjecI'm getting this issue with an insert statement using Kysely. But I'm not sure what I'm doing wrong.Reuse subquery selectsHello, I am trying to correctly type a select subquery to use in different other queries: ```ts expoUse JSON key as textHello, I am using the following syntax to fetch data in a JSONB column: ```ts .select((eb) => [ 'iTyping issue when working with onConflictHi, so i was trying to use some of examples https://kysely-org.github.io/kysely-apidoc/classes/Insergetting Error: relation "myschema.kysely_migration_lock" does not existHey, I am trying to use the migrator.migrateToLatest() to migrate a DB for me and it is failing withWhat is easiest way to get count alongside data for pagination?I am running second query with raw sql `SELECT COUNT(id) from table_name`. is this legit?Reusable CTEs that depend on previous CTEsI have 2 CTEs that I'd like to separate out into separate functions for readability. These are the `update set from`Accroding to @koskimas, kysely supports the `update set from` sql syntax. So far, I've been unable tAre JSON Arrays not supported for paramterization in the postgres driver?Hey! I've been loving kysely so far and doing a ton of awesome code cleanup and refactoring with it.Combining selectAll and arbitrary expressionsHey! Is it possible to combine `selectAll` (for selecting all columns of a couple of tables) and theHow to plugin column aliases with table prefix?I am not familiar with kysely plugin creation. I am also not sure if my issue can be done with such Query building optimization questionHello, Id like some input from Kysely advanced users about my approach to using the `eb`. If I have Why is eb inferred as any in this update query?I'm trying to do an update from values inserted in a CTE. But the update's set operation doesn't seeCan you mix kysely with pg transactions?For example: ``` try { await client.query('BEGIN') <KYSELY CODE HERE> await client.query(Zero number omitted from parametersI looks like the compile() works incorrectly with zero number when used as parameter - the parameterIs there any way to postgres notify and listen with kysley?I want to use `pg.on` but I would create a new instance connection just for that. It would be much bbool_orHi, How to use bool_or ? I am using it in case-when-then construction. https://www.postgresql.org/d