Open schwartzmj opened 1 year ago
Interesting problem! I can see the use case.
One possible solution would be to add a helper type to kysely-codegen
(or your own codebase) that converts Kysely-specific types into generic types (general idea - I haven't tested this):
export type Model<T extends Record<PropertyKey, unknown>> = {
[K in keyof T]: T[K] extends ColumnType<infer S, any, any> ? S : T[K];
};
export type ExampleModel = Model<Example>;
// {
// id: number;
// created_at: Date;
// updated_at: Date;
// text: string;
// };
One possible solution would be to add a helper type to kysely-codegen (or your own codebase) that converts Kysely-specific types into generic types (general idea - I haven't tested this):
There's a Selectable
helper in Kysely that does just this:
From the example:
import type { Selectable } from 'kysely'
import type { Examples } from "@db/schema";
type ExampleProps = {
example: Selectable<Examples>;
};
To convert the whole DB:
import { Selectable } from 'kysely'
import type { DB } from './path-to-kyseley.gen'
type DBSelect = {
[K in keyof DB]: Selectable<DB[K]>
}
There's a
Selectable
helper in Kysely that does just this:
Interesting! Is this a good enough solution for your use case @schwartzmj?
I'd really appreciate kysely-codegen generating something like
export type Examples = { ... }
export type Example = Selectable<Examples>
This is how I did it when I wrote the types manually -- we won't have to put Selectable<...> everywhere, which gets cumbersome if you need other helper types quickly (imagine Required<Omit<Selectable<Examples>, "foo">>
)
I wrote this abomination:
type Model<T> = {
[K in keyof T]: T[K] extends ColumnType<infer S, any, any> ? S : T[K];
};
async function getRow<T extends keyof DB>(
table: T,
whereParams: Partial<{
[s in keyof DB[T]]: Model<DB[T]>[s];
}>,
selectors: Array<keyof DB[T]>
): Promise<DB[T]> {
const row = kysley.selectFrom(table);
for (const key of Object.keys(whereParams)) {
row.where(
key as ReferenceExpression<DB, ExtractTableAlias<DB, T>>,
'=',
(whereParams as { [s: string]: any })[key]
);
}
row.select(selectors as SelectExpression<DB, ExtractTableAlias<DB, T>>[]);
return (await row.executeTakeFirst()) as DB[T];
}
This allowed me to write functions like the below, with a good degree of type safety
const { creditValue } = await sql.getRow(
'template',
{ id: formData.templateID },
['creditValue']
);
The only thing I'm figuring out is how to get the third argument (which is the selectors I want to use), to properly restrict the properties on the return object. Not quite there yet.
edit: I tried more, but hitting a brick wall. going to stick with the code above for now
type Model<T> = {
[K in keyof T]: T[K] extends ColumnType<infer S, any, any> ? S : T[K];
};
async function getRow<T extends keyof DB, U extends Array<keyof DB[T]>>(
table: T,
whereParams: Partial<{
[s in keyof DB[T]]: Model<DB[T]>[s];
}>,
selectors: U
) {
const row = kysley.selectFrom(table);
for (const key of Object.keys(whereParams)) {
row.where(
key as ReferenceExpression<DB, ExtractTableAlias<DB, T>>,
'=',
(whereParams as { [s: string]: any })[key]
);
}
row.select(selectors as SelectExpression<DB, ExtractTableAlias<DB, T>>[]);
return (await row.executeTakeFirst()) as { [k in keyof U]: DB[T][k] };
}
edit... improved... sticking with this one instead:
type Model<T> = {
[K in keyof T]: T[K] extends ColumnType<infer S, any, any> ? S : T[K];
};
async function getRow<T extends keyof DB>(
table: T,
whereParams: Partial<{
[s in keyof DB[T]]: Model<DB[T]>[s];
}>,
selectors?: Array<keyof DB[T]>
): Promise<Model<DB[T]>> {
const row = kysley.selectFrom(table);
for (const key of Object.keys(whereParams)) {
row.where(
key as ReferenceExpression<DB, ExtractTableAlias<DB, T>>,
'=',
(whereParams as { [s: string]: any })[key]
);
}
row.select(selectors as SelectExpression<DB, ExtractTableAlias<DB, T>>[]);
return (await row.executeTakeFirst()) as Model<DB[T]>;
}
It would be awesome + in-line with the intended use of Kysely if kysely-codegen exported the selectable insertable and updateable types. Something like this:
export interface UserTable {
id: Generated<string>
...
}
export type User = Selectable<UserTable>
export type NewUser = Insertable<UserTable>
export type UserUpdate = Updateable<UserTable>
This is how we instruct people to structure their types in the docs.
+1
Let's say I have a table called
Examples
and the generated types look like this:If I'm using something like React or Svelte, and I want to type a prop e.g.:
I'll get errors like the following for the
Generated<number>
on theid
column (in this example, trying to set thevalue
attribute on aninput
element):Any way around this? Would it be best to create a file of different queries and infer the type of each result and export those:
This seems a bit odd, however if I have different queries e.g. one where I
selectAll
vs selecting certain fields, it seems great to have those typed separately and then I can just import the query from the file as well.Upvote & Fund