What are the Drizzle conventions for giving names to Drizzle return types?

Hello all, I have a question about Drizzle conventions. Do people give names to the return types of their Drizzle calls? For example, consider the following code:
import { eq } from "drizzle-orm";
import { usersTable } from "../databaseSchema";
import { db } from "../db";

export type User = NonNullable<Awaited<ReturnType<typeof users.get>>>;

export const users = {
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
get: async (username: string) => {
const rows = await db
.select({
id: usersTable.id,
username: usersTable.username,
passwordHash: usersTable.passwordHash,
})
.from(usersTable)
.where(eq(usersTable.username, username))
.limit(1);

return rows[0];
},
};
import { eq } from "drizzle-orm";
import { usersTable } from "../databaseSchema";
import { db } from "../db";

export type User = NonNullable<Awaited<ReturnType<typeof users.get>>>;

export const users = {
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
get: async (username: string) => {
const rows = await db
.select({
id: usersTable.id,
username: usersTable.username,
passwordHash: usersTable.passwordHash,
})
.from(usersTable)
.where(eq(usersTable.username, username))
.limit(1);

return rows[0];
},
};
- Here, we use the ReturnType utility type from TypeScript to satisfy DRY. (If we wanted to violate DRY, we could repeat all the user fields inside of a dedicated User TypeScript interface.) - However, we now have a problem where Intellisense does not work properly. In other words, if we mouse over user in the following code:
const user = users.get("foo");
const user = users.get("foo");
We get the full enumerated object inside of a union with undeifned, instead of the much easier to read and understand User | undefined. Does anyone know if there are Drizzle conventions to get smarter Intellisense here?
23 Replies
균어
균어11mo ago
you can use
const rows = await db.query.usersTable.findFirst()
const rows = await db.query.usersTable.findFirst()
Angelelz
Angelelz11mo ago
I would create a explicit type and export it. Then use that type as the return type of the get function
Zamiel
Zamiel11mo ago
Ok, thanks. It just kind of sucks because user has like 20 fields and now I have to duplicate everything, violating DRY, and since they are separated the two things can get out of sync, i.e. the manually created interface can have excess fields.
Angelelz
Angelelz11mo ago
You don't need to explicitely create the type
Zamiel
Zamiel11mo ago
Oh. Can you show an example?
Angelelz
Angelelz11mo ago
I'm guessing you already have a User type in your schema like this:
export type User = typeof usersTable.$inferSelect
export type User = typeof usersTable.$inferSelect
Zamiel
Zamiel11mo ago
I don't already have that, no. That inferSelect is a nice trick.
Angelelz
Angelelz11mo ago
You can use that type and do:
export type PartialUser = Pick<User, "id" | "username" | "passwordHash">
export const users = {
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
get: async (username: string): PartialUser[] => {
const rows = await db
.select({
id: usersTable.id,
username: usersTable.username,
passwordHash: usersTable.passwordHash,
})
.from(usersTable)
.where(eq(usersTable.username, username))
.limit(1);

return rows[0];
},
};
export type PartialUser = Pick<User, "id" | "username" | "passwordHash">
export const users = {
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
get: async (username: string): PartialUser[] => {
const rows = await db
.select({
id: usersTable.id,
username: usersTable.username,
passwordHash: usersTable.passwordHash,
})
.from(usersTable)
.where(eq(usersTable.username, username))
.limit(1);

return rows[0];
},
};
Zamiel
Zamiel11mo ago
Ah, right. So that's slightly better than doing:
export type PartialUser {
id: number;
username: string;
passwordHash: string;
}
export type PartialUser {
id: number;
username: string;
passwordHash: string;
}
In that I'm only repeating the names of the fields, instead of both the names of the fields and the types of the fields. But I still have to duplicate 20+ names inside of the Pick.
Angelelz
Angelelz11mo ago
Maybe the type anotation should be Promise<PartialUser[]>, you can check it As long as you are partially selecting the user I wonder why your type there is not getting properly picked up
Zamiel
Zamiel11mo ago
What do you mean?
Angelelz
Angelelz11mo ago
This one
Zamiel
Zamiel11mo ago
Um It is "properly" being picked up, it just is ugly, because its a union of like 20+ different things and destroys the mouseover. And you can't manually annotate the get function as returning User[] because TypeScript complains that you can't have recursive types. If that doesn't make sense I can try to explain better
Angelelz
Angelelz11mo ago
No I get it. You're attempting to define a type using it's own definition Can you show a screenshot of the type you are getting? The problem is that you have a lot of columns?
Zamiel
Zamiel11mo ago
No description
Zamiel
Zamiel11mo ago
@Angelelz That's the type, but just imagine with 20+ fields instead of 3. What I want is to have User | undefined, which makes it much easier to reason about, and so that it matches the name of the variable (user).
Angelelz
Angelelz11mo ago
I thought you weren't getting you type correctly There's nothing you can do if you're inferring the type How is typescript supposed to know that that type is called User?
Zamiel
Zamiel11mo ago
Right, which is exactly the premise of my question. I was envisioning something like setting up the shape of the SELECT call beforehand, such that the type interference can be stored in a User type, and then during runtime inside of the actual get function I can re-use the SELECT schema. Is something like that possible? (The inferSelect is a nice trick though that gets me half of the way there at least.)
Angelelz
Angelelz11mo ago
Yeah, I mean, people usually complain about the opposite, seeing User | undefined but wanting to see the keys. There is no other way, you either infer the type and get the keys explicitly or use the $inferSelect type helper in combination with Omit or Pick utility types to anotate the return type of your function Now, if you were not selecting a partial table, maybe typescript would give you the User type, but I'm not sure No, maybe not, because how would it know that it's called User
Zamiel
Zamiel11mo ago
Yeah, I mean, people usually complain about the opposite, seeing User | undefined but wanting to see the keys.
Oh, that's ironic, lol I can't convert the OP to findFirst because it has a where clause, right?
Angelelz
Angelelz11mo ago
You can use a where in a findFirst
균어
균어11mo ago
you can use where in query
const rows = await this.db.query.users.findFirst({
columns: {
id: usersTable.id,
username: usersTable.username,
passwordHash: usersTable.passwordHash,
},
where: eq(usersTable.username, username)
})
const rows = await this.db.query.users.findFirst({
columns: {
id: usersTable.id,
username: usersTable.username,
passwordHash: usersTable.passwordHash,
},
where: eq(usersTable.username, username)
})
Zamiel
Zamiel11mo ago
ah, thanks to be honest i think i'll just stick with the normal sql code, its easier to read and one less layer of abstraction
Want results from more Discord servers?
Add your server