Thanks for your work on Kysely. I have a question about creating a generic shared function that returns generated columns from the table. I want to use this to build a class that contains basic CRUD functionality for my app. I've tried a number of ways and am hoping you have some deeper knowledge of the Typescript typing incantation necessary that I do.
Goal
I'd like to create a function that inserts an object and returns a generated column (in this example id, but in my real use case id and createdAt -- I know there is already built in functionality for an autogenerated id column).
My first try was this:
async function createAndReturnId<DB, TableName extends keyof DB & string>(
db: Kysely<DB>,
table: TableName,
object: Insertable<DB[TableName]>
): Promise<{ id: string }> {
const result = await db
.insertInto(table)
.values(object)
// Error: Argument of type '"id"[]' is not assignable to parameter of type 'SelectArg<DB, TableName, SelectExpression<DB, TableName>>'.
.returning(["id"])
.executeTakeFirstOrThrow();
return result;
}
Kysely doesn't know that the id column can be selected from DB[TableName]. The full error is:
Argument of type '"id"[]' is not assignable to parameter of type 'SelectArg<DB, TableName, SelectExpression<DB, TableName>>'.
Type '"id"[]' is not assignable to type 'readonly SelectExpression<DB, TableName>[]'.
Type 'string' is not assignable to type 'SelectExpression<DB, TableName>'.ts(2345)
Attempts
If I constrain the DB to require that every table has an id field, I can write the function:
async function createAndReturnIdAll<
DB extends { [K in keyof DB]: { id: string } },
TableName extends keyof DB & string
>(
db: Kysely<DB>,
table: TableName,
object: Insertable<DB[TableName]>
): Promise<{ id: string }> {
const result = await db
.insertInto(table)
.values(object)
.returning(["id"])
.executeTakeFirstOrThrow();
return result;
}
This compiles without any errors.
However, this is over-constraining DB. I'd like to only require that the TableName table has the ID field. I'm not sure how to write this type constraint. I tried below:
async function createAndReturnIdJustTN<
DB extends { [K in keyof DB]: K extends TableName ? { id: string } : DB[K] },
TableName extends keyof DB & string
>(
db: Kysely<DB>,
table: TableName,
object: Insertable<DB[TableName]>
): Promise<{ id: string }> {
const result = await db
.insertInto(table)
.values(object)
.returning(["id"]) // Error
.executeTakeFirstOrThrow();
return result;
}
It seems that this doesn't help the Typescript compiler at all. There is still the same error as before.
Is there a better way to write the constraint on DB such that the compiler is able to understand the SelectExpression type appropriately for the returning builder method?
Workaround
Currently I am using the db.dynamic feature with some type casing as a workaround for this.
Hi,
Thanks for your work on Kysely. I have a question about creating a generic shared function that returns generated columns from the table. I want to use this to build a class that contains basic CRUD functionality for my app. I've tried a number of ways and am hoping you have some deeper knowledge of the Typescript typing incantation necessary that I do.
Goal
I'd like to create a function that inserts an object and returns a generated column (in this example
id
, but in my real use caseid
andcreatedAt
-- I know there is already built in functionality for an autogenerated id column).My first try was this:
Kysely doesn't know that the
id
column can be selected fromDB[TableName]
. The full error is:Attempts
If I constrain the DB to require that every table has an
id
field, I can write the function:This compiles without any errors.
However, this is over-constraining DB. I'd like to only require that the
TableName
table has the ID field. I'm not sure how to write this type constraint. I tried below:It seems that this doesn't help the Typescript compiler at all. There is still the same error as before.
Is there a better way to write the constraint on
DB
such that the compiler is able to understand theSelectExpression
type appropriately for thereturning
builder method?Workaround
Currently I am using the
db.dynamic
feature with some type casing as a workaround for this.