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
A few ideas and thank you #5
Comments
That's an excellend idea and I've considered it a lot. The problem is values generated in the database, like If we required all required fields in the insert, in this example we'd also need to require the The options: Separate interfaces for insert and select (and update?)This is the same as your solution. This would cause the types to be at least twice as complex everywhere. We would need to pass around at least two DecoratorsDecorators are a runtime feature and since Kysely uses interfaces for which there is no runtime code, we cannot use decorators like this: interface Person {
@generated
id: number
} Wrapper typesinterface Person {
id: Generated<number>
} This could maybe be possible, but it would probably cause problems where Based on small tests, something like this seems to work: type Generated<T> = T & { __generated__?: true }
const x: Generated<number> = 1
const y: number = x but you can access the fake property Generated specifier for insertsIn this option, the fields of the insert object are not optional. Instead, you need to either pass a value or a await db.insertInto('person').values({
id: db.generated,
created_at: db.generated,
name: 'Testerson',
}) Any thoughts on this option?
Yes, I've considered adding a |
I really like the |
db.generated` is better and easier to implement :-)
I think either slack or discord will do the job |
I think discord is more popular for OSS projects these days 😉 |
All fields are now required for inserts in the latest 0.3.5 version and the await db.insertInto('person').values({
id: db.generated,
created_at: db.generated,
name: 'Testerson',
some_nullable_column: null,
}) I'll revisit the optionality later. |
@xiaoyu-tamu @tonivj5 There's now a discord badge in the readme. I don't know if I set it up correctly. Could you try it out? |
Weird. For me it opens a channel (there are a few). Maybe I need use the invite link in the badge? (I've never used discord). Here's the invite link https://discord.gg/C9aJ49mCra. Does that go to the right place? |
The invite link works. |
I have one idea how to solve this issue on the type level. I’ve developed it some time ago, but haven’t had time to integrate it into our internal project yet. I’ll dig it out once I have a while. The main idea is to use generic wrapper types to encode metadata (such as PK), but in a way that doesn’t require any type casting when used, i.e. it behaves exactly like the wrapped type for the users. These wrappers are used in helper types that transform the model type into derived types (insertable, selectable etc.) and even allow things like “expanding” references. It’s sorta black-magic-like typing, but it worked wonderfully. However, it need testing in real scenarios. I have a bit an unpleasant experience with TypeScript in the way that any complicated types fall apart in various corner cases, especially when you try to combine them with any other non-trivial types. |
@jirutka I'd love to see your solution! |
I created a typings and shortcuts generator that using kysely for kysely over this weekend. This library is still work in progress and docs and tests are still missing. But the database introspection is ready to use. https://github.com/xiaoyu-tamu/ormless/tree/main/example import { CustomerRepository, Database, DatabaseSchema } from './database';
const db = new Kysely<DatabaseSchema>({
database: process.env.DATABASE_NAME!,
host: process.env.DATABASE_HOST!,
user: process.env.DATABASE_USER!,
password: process.env.DATABASE_PASSWORD!,
dialect: 'postgres',
plugins: [new CamelCasePlugin()],
});
// select unique customer by primary key
const fetchedCustomer = await customerRepo.selectOne({
db,
select,
where: { customerPkey: { customerId: -1 } },
});
// update unique customer by unique key
const updatedCustomer = await customerRepo.updateOne({
db,
select,
where: { customerEmailUk: { email: 'lookup@email.com' } },
data: { activebool: true },
}); |
How about something like this: import { table, t, Table, Selectable, Insertable, Updatable, Default } from "kysely";
export const Person = table({
id: t.number("BIGINT").primary(), // table could ensure that table schema object contains exactly 1 primary column
name: t.varchar(123).nullable(), // default length could be specified like this
age: t.number("INT").default(18), // `number` is a little vague for a db so internal number type could be specifiable like this (in typesafe manner ofc)
});
export type Person = Table<typeof Person>; // this type would contain "metadata" (ex. `__generated__`, `__default__` ) that would be stripped out by below types
///
export type PersonSelectable = Selectable<Person>; // { id: readonly number; name: string | null; age: number; }
export type PersonInsertable = Insertable<Person>; // { name: string | null; age: number | Default }
export type PersonUpdatable = Updatable<Person>; // { name?: string | null; age?: number } This is a long shot but a system like this would improve type-safety and would allow for automatic (at least semi-automatic) migrations generation |
@elderapo Your suggestion is a different project completely. Kysely aims to be just a query builder. It builds and executes queries, nothing more. I don't see Kysely ever having that kind of model/automigration system. In my experience, as soon as you start building abstraction layers on top of SQL, things get order of magnitude harder. You need to either drop a vast majority of SQL features or build weird complex abstractions that support all or at least most of them. You either get a shitty product (most node.js ORMs) or a massive one (Hibernate). |
@koskimas I think you misunderstood what I meant :( The part about auto migrations was solely a side effect (in fact cool/useful in my opinion but that's besides my point) of having a strongly defined schema. The main purpose of this strongly defined schema (ex.
Essentially the |
Yes, the hard part is implementing the For example, see the types in Sure, the {
__insertable__: { foo: string }
__selectable__: { foo: string, id: string }
} and then type Selectable<T extends { __selectable__: any }> = T['__selectable__'] that's easy. But using these internally wouldn't be. |
I totally agree that such a change would increase the complexity of the internal/library code but would also drastically improve the type-safety of the userland code which is the main purpose of this library, no? I've quickly created a simple proof of concept of the |
I don't believe it would improve type safety beyond the current solution (which requires all nullable columns with type It seems it would only make things a bit more convenient because you could omit generated or nullable columns in those calls. (In #27 I note that you can avoid specifying nullable columns if you make them optional fields instead of a null-union, but I'm not clear on all the ramifications of that choice.) |
@martinblostein If you make:
|
I don't understand, if you type If your issue is that |
|
I understand how generated columns work. What I'm saying is that there is a type-safe way of handling them in
Then when you insert into that table, you use
|
@martinblostein please read my message again. I've initially misread your message and then edited it. |
Ah, yes I see. You have a good point about how I'm hopefully that could be fixed by introducing using a The reason I'm hopeful that a |
@elderapo Your suggestion is cool, but we have to consider the tradeoff here. Your suggestion would achieve slightly better type-safety since currently you can pass You can use Currently the types, while complex, are still manageable. The goal of Kysely is not to be 100% type-safe at all cost, but remain usable and maintainble while being "type-safe enough". |
I think this kind of general discussion should be continued in discord. I'll close this issue now. |
Hi there, I have been trying this lib this weekend. By far, this is the most powerful and lightweight type-safe sql query builder IMO. (compares to knex, zapatos and slonik)
I'm using
prisma2
in production but would love to migrate some of the queries to kysely once it become stable.I love the amazing work you have done and I think this work could be improved if it could support stricter type.
Assuming i have the following DDL
I would expect the following code throw because name non-nullable
The current behavior typescript does not raise exception because the method
value
shape isPartial<Model>
.So, Instead of passing single database model definition to
new Kysely<Models>
, it would be nice to allow user pass both selectable definition and insertable definition in the future. Then we could get stricter type for both insert and update mutation.Also, do you have any plan to build/integrate database typing generator into this library? I'm using patched version of
sql-ts
for now, it works great but it has some limitations. I could help on that if you likeKeep up the good work 👍
The text was updated successfully, but these errors were encountered: