kysely-org / kysely

A type-safe typescript SQL query builder
https://kysely.dev
MIT License
10.71k stars 271 forks source link

'.if()' Does Not Alert Typescript of Checks Being Made #164

Closed alrightsure closed 2 years ago

alrightsure commented 2 years ago

Given the following interfaces:

interface PersonTable {
    id: Generated<number>;
    first_name: string;
    last_name: string;
    gender: "male" | "female" | "other";
}

interface Database {
    person: PersonTable;
}

const db = new Kysely<Database>({
    dialect: new PostgresDialect({
        pool: new Pool({
            host: ""
        })
    })
});

The following function should allow the variable 'firstName' to be used in the where clause

async function selectPerson(gender: "male" | "female" | "other", firstName?: string | null) {
    return await db
        .selectFrom("person")
        .where("gender", "=", gender)
        .if(firstName !== null && firstName !== undefined, qb => qb.where("first_name", "=", firstName))
        .execute();
}

However, the built in '.if()' function kysely does not alert typescript that 'firstName' is a string rather than null or undefined.

Argument of type 'string | null | undefined' is not assignable to parameter of type 'FilterValueExpressionOrList<From<Database, "person">, "person", "first_name">'.
  Type 'undefined' is not assignable to type 'FilterValueExpressionOrList<From<Database, "person">, "person", "first_name">'.ts(2345)

After playing with kysely for a bit, there doesn't seem to be a good way to do this other than have the entire query written twice with an external if check. Please let me know if I am misguided or doing something wrong.

Would love to use this library on a larger sized project at work, but there are a lot of instances where we need to check for optional props being passed to the calling function.

alrightsure commented 2 years ago

Another note, I have also tried the following:

async function selectPerson(gender: "male" | "female" | "other", firstName?: string | null) {
    const query = db.selectFrom("person").where("gender", "=", gender);

    if (firstName) {
        query.where("first_name", "=", firstName);
    }

    return await query.execute();
}

selectPerson("male", "charles");

However, the second where clause is completely ignored from the query, even when firstName is passed. It seems any part of the query must be added to the initial chain.

alrightsure commented 2 years ago

updating as open as I closed the issue by mistake.

koskimas commented 2 years ago

Unfortunately it's impossible to alert typescript of the checks. This is a perfect place for !:

.if(firstName !== null && firstName !== undefined, qb => qb.where("first_name", "=", firstName!))

Everything is immutable in kysely, so you need to assign the results of operations back:

async function selectPerson(gender: "male" | "female" | "other", firstName?: string | null) {
    let query = db.selectFrom("person").where("gender", "=", gender);

    if (firstName) {
        query = query.where("first_name", "=", firstName);
    }

    return await query.execute();
}

selectPerson("male", "charles");