Subquery type error when using generics

KNKristian Notari3/15/2023
I have the following simple query being generated by my function f:
const f = () => queryBuilder
            .deleteFrom(`table as t1`)
            .whereExists(qb => qb.selectFrom(`table as t2`)
                .whereRef('t1.col1', '>=', 't2.col1')
            )

and it works fine. If I add a generic for reusing the same function with different tables (all sharing the common col1 column) it works fine, until I get to the sub query whereRef (even if I constraint the generic to a single value, which is the same table as before):
const f = <T extends 'table'>(table: T) => queryBuilder
            .deleteFrom(`${table} as t1`)
            .whereExists(qb => qb.selectFrom(`${table} as t2`)
                /* here it can't find the refs to t1.col1 or t2.col1 */.whereRef('t1.col1', '>=', 't2.col1')
            )
KNKristian Notari3/15/2023
Argument of type 'string' is not assignable to parameter of type 'ReferenceExpression<From<From<DB, `${T} as old`>, "${T} as new">, FromTables<From<DB, `${T} as old`>, ExtractAliasFromTableExpression<DB, `${T} as old`>, "${T} as new">>'.ts(2345)
KNKristian Notari3/15/2023
I found out that if you spread the T over an union type as the generic of the first method of the query builders (eg. deletedFrom and selectFrom), then everything works as expected:
const f = <T extends 'table1' | 'table2'>(table: T) => queryBuilder
            .deleteFrom<'table1 as t1' | 'table2 as t1'>(`${table} as t1`)
            .whereExists(qb => qb.selectFrom<'table1 as t2' | 'table2 as t2>(`${table} as t2`)
                /* here it can't find the refs to t1.col1 or t2.col1 */.whereRef('t1.col1', '>=', 't2.col1')
            )

This works
KNKristian Notari3/15/2023
And you can easily construct such generic types for first methods of query builder as:
type GenericAliasFrom<T extends string, A extends string> = `${T} as ${A}`

where T should be 'table1' | 'table2' and not the generic T type inside the function itself, otherwise it would not work
IIgal3/16/2023
Hey 👋

Is this resolved?

What is the reasoning behind the helper/s here? Seems like a case of premature / overly complex DRY application.
KNKristian Notari3/17/2023
As I said, I need to do the same exact query for multiple tables that share a set of columns (maybe I want to update the updated_at column, or set the "deleted_at" column, etc). The problem being, if kysely builder first method get its generic from some generic T it can't successfully hint (and accept) any string references then inside where conditions and such. By explicitly setting the first type parameter to the deleteFrom or selectFrom and so on, as the distributed union when aliasing such generic T you then get correct type inference for the rest of the query builder methods.
KNKristian Notari3/17/2023
This surely happens when you're aliasing a generic table type, so as
`${T} as t1`
for example
KNKristian Notari3/17/2023
As long as it concerns me, I found out how to overcome this type inference problem by explicitly annotating the query builder method as I showed before. Not sure if this is something worth investigating further in order to make the type annotation unnecessary.
KNKristian Notari3/17/2023
It's still type safe, even when annotating it explicitly, I'm just redistributing the template literal type, nothing fancy there
Kkoskimas3/17/2023
Seems like something that's very difficult to get working with kysely. And also something you maybe shouldn't try to do with it. You can either have super strict types, or super generic types. Not both unfortunately
KNKristian Notari3/17/2023
Yeah I mean, it's all about not being too generic. Instantiating the right type helping it by using explicit annotation is doable and type safe still so, why not?
Kkoskimas3/17/2023
Often these become unreadable, unmaintainable and impossible for other people to understand even if you get them working. At some point those things out weight type-safety