Problems typing a generic withPerson helper

First I want to say that I'm in love with Kysely! It's amazing. I could use some help getting the types right for a helper function I'm creating. I'm trying to follow the relations recipe (the withPets and withMom example from the docs), but I want to make it slightly more reusable. I have a withPerson method
export function withPerson<As extends string>(
builder: ExpressionBuilder<DB, keyof DB>,
personIdCol: ReferenceExpression<DB, keyof DB>,
as: As = 'person' as As
) {
return jsonObjectFrom(
builder
.selectFrom('Person')
.whereRef('Person.id', '=', personIdCol)
.select([
'Person.id',
'Person.givenName',
'Person.familyName',
'Person.picture80',
'Person.picture144'
])
).as(as);
}
export function withPerson<As extends string>(
builder: ExpressionBuilder<DB, keyof DB>,
personIdCol: ReferenceExpression<DB, keyof DB>,
as: As = 'person' as As
) {
return jsonObjectFrom(
builder
.selectFrom('Person')
.whereRef('Person.id', '=', personIdCol)
.select([
'Person.id',
'Person.givenName',
'Person.familyName',
'Person.picture80',
'Person.picture144'
])
).as(as);
}
When I try and use it like this
const result = db
.selectFrom('Prospect')
.select((eb) => withPerson(eb, 'person'))
.execute();
const result = db
.selectFrom('Prospect')
.select((eb) => withPerson(eb, 'person'))
.execute();
I get a typescript error: Argument of type 'ExpressionBuilder<DB, "Prospect">' is not assignable to parameter of type 'ExpressionBuilder<DB, keyof DB>'
3 Replies
autobotkilla
autobotkilla15mo ago
So I tried making the function more generic.
export function withPerson<
TDB extends DB,
TTB extends keyof TDB,
As extends string>(
builder: ExpressionBuilder<TDB, TTB>,
personIdCol: ReferenceExpression<TDB, TTB>,
as: As = 'person' as As
) {
return jsonObjectFrom(
builder
.selectFrom('Person')
.whereRef('Person.id', '=', personIdCol)
.select([
'Person.id',
'Person.givenName',
'Person.familyName',
'Person.picture80',
'Person.picture144'
])
).as(as);
}
export function withPerson<
TDB extends DB,
TTB extends keyof TDB,
As extends string>(
builder: ExpressionBuilder<TDB, TTB>,
personIdCol: ReferenceExpression<TDB, TTB>,
as: As = 'person' as As
) {
return jsonObjectFrom(
builder
.selectFrom('Person')
.whereRef('Person.id', '=', personIdCol)
.select([
'Person.id',
'Person.givenName',
'Person.familyName',
'Person.picture80',
'Person.picture144'
])
).as(as);
}
This fixes the typescript error when I call the function, but messes up the typing inside the function on the .whereRef and .select calls. Argument of type 'string' is not assignable to parameter of type 'ReferenceExpression<TDB, TTB | ("Person" extends keyof TDB ? keyof TDB & "Person" : never)>'. I just can't seem to figure this out. I'd really appreciate any help. I'm trying to make it so that any table that has a foreign key to the person table can load the same person data, but pass in the column reference for that table. For example, Message.senderId or Project.ownerId both point to the person table. Here's a repro on Kysely playground https://kyse.link/?p=s&i=8gXYLeE8i8LQVeEFSjsv
Igal
Igal15mo ago
Hey 👋 Thanks for the playground repro, will take a look wasn't able to crack it yet. seems like classic TS not narrowing stuff in a function hack for now https://kyse.link/?p=s&i=tsOS8FXi0bV5jtkHG7vc
autobotkilla
autobotkilla15mo ago
Thanks for looking at this @Igal. The workaround isn't terrible actually since it allows me to limit the callers to tables with a foreign key.