Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Importing type from generated types #63

Open
schwartzmj opened this issue Feb 18, 2023 · 7 comments
Open

Importing type from generated types #63

schwartzmj opened this issue Feb 18, 2023 · 7 comments
Labels
feature request New feature or request

Comments

@schwartzmj
Copy link

schwartzmj commented Feb 18, 2023

Let's say I have a table called Examples and the generated types look like this:

import type { ColumnType } from "kysely";

export type Generated<T> = T extends ColumnType<infer S, infer I, infer U>
  ? ColumnType<S, I | undefined, U>
  : ColumnType<T, T | undefined, T>;

export interface Examples {
  id: Generated<number>;
  created_at: Generated<Date>;
  updated_at: Generated<Date>;
  text: string;
}

export interface DB {
  examples: Examples;
}

If I'm using something like React or Svelte, and I want to type a prop e.g.:

import type { Examples } from "@db/schema";

type ExampleProps = {
  example: Examples;
};

I'll get errors like the following for the Generated<number> on the id column (in this example, trying to set the value attribute on an input element):

2023-02-18 at 14 44 10

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:

const exampleQuery = await db.selectFrom('examples').selectAll().execute();
export type Example = (typeof exampleQuery)[number];

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

  • We're using Polar.sh so you can upvote and help fund this issue.
  • We receive the funding once the issue is completed & confirmed by you.
  • Thank you in advance for helping prioritize & fund our backlog.
Fund with Polar
@RobinBlomberg RobinBlomberg added the feature request New feature or request label Feb 19, 2023
@RobinBlomberg
Copy link
Owner

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;
// };

@tgriesser
Copy link

tgriesser commented Mar 9, 2023

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]>
}

@RobinBlomberg
Copy link
Owner

There's a Selectable helper in Kysely that does just this:

Interesting! Is this a good enough solution for your use case @schwartzmj?

@merlindru
Copy link

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">>)

@jpike88
Copy link

jpike88 commented May 13, 2023

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]>;
}

@koskimas
Copy link
Contributor

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.

@sjunepark
Copy link

+1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature request New feature or request
Projects
None yet
Development

No branches or pull requests

7 participants