drizzle-team / drizzle-orm

Headless TypeScript ORM with a head. Runs on Node, Bun and Deno. Lives on the Edge and yes, it's a JavaScript ORM too 😅
https://orm.drizzle.team
Apache License 2.0
22.86k stars 547 forks source link

[FEATURE]: Predefining selects and infering types from it using drizzle-zod #2084

Open SchmellaKaa opened 5 months ago

SchmellaKaa commented 5 months ago

Describe what you want

Hey, i recently started a new project using drizzle and would like to create some select statements that can be reused in different places (without the need to each time specify the select separately on a db.select({...}) call) I would need the types to share them with my frontend and use the zod schema to validate user inputs passed to api requests...

The thing is, that createSelectSchema requires a table as input to create a zod schema from it. So i can only get the selectSchema for all fields of a table... How would i go about getting a selectSchema with only certain fields of a table?

I tried the following approach (which seems a bit hacky to me, but i did not find a better approach for it in the documentation)... I defined a minimalSelect that can be used with db.select() and create an input for selectSchema.pick() out of the select definition. With the minimalSelectSchema i can then infer a MinimalUser type with only certain fields.

import { pgTable, uuid, varchar } from "drizzle-orm/pg-core";
import { createSelectSchema } from "drizzle-zod";
import { z } from "zod";

export const userTable = pgTable("adminRole", {
    id: uuid("id").primaryKey().defaultRandom(),
    name: varchar("name", { length: 256 }),
    password: varchar("password", { length: 256 }),
    // ...
});
export const userSelectSchema = createSelectSchema(userTable);

export const minimalUserSelect = {
    id: userTable.id,
    name: userTable.name,
    // no password
    // ...
};
export const minimalUserSelectSchema = userSelectSchema.pick({
    ...Object.keys(minimalUserSelect).reduce((acc, key) => Object.assign(acc, { [key]: true }), {} as Record<keyof typeof minimalUserSelect, true>),
});
export type MinimalUser = z.infer<typeof minimalUserSelectSchema>;

Is there a better way to approach this? In the documentation and many examples predefined select objects are never mentioned or used. Is this such an exotic usecase?

Would it be an option to allow for createSelectSchema to take a definition of fields as input instead of a whole table?

export const minimalUserSelectSchema = createSelectSchema({id: userTable.id, name: userTable.name });
gunhaxxor commented 2 months ago

Perhaps I'm misunderstanding, but I dont understand why you go through the hassle with the Object.keys reduce thingy? You can simply use the zod pick or omit directly: https://www.typescriptlang.org/play/?ts=5.4.5#code/JYWwDg9gTgLgBAbzmA5gFQIYCMA2BTAGjgFdjgATIgNwygGMALWuAXzgDMoIQ4AicqMABeQ-AFpoIAPSoxdaHl4BuAFChIsRHDpQ8GGHgDKefHRiHGeEBlYcuPfoJHihEcsrXho8JENuduPld3VRU8AA8NeHkAOwBneGI4vChMXDw4AF5kdGx8AApeDHIQYBiAJQh8XiIEFTgGuAoALhIyckKKXgBKADowQWsoAE8AaTxh-L7yPHYMYhwYcowY8m4pgnrGmIwQPFaaeiYoQp29mq18GJQYBlaAJgBWADZWbs3G5Aw4uIB3aHIB1ojFohTA3z+AIuSCuNzucCerxY7y2DSwwAgQKOoN46Ig0Nht1aAEYAAz3AAsyJUyNUESi2gg8USySgxlM5ks1iy2l0+iMJjwZgsDCsGHySRSaXw3VUsQScF+wFuEGIMAAChD-lByABVVnsoWc0Xc7KStmC4VcjC9bjK-IIcE-bWAuAwKDEPDUlQwYZgDL6lIAdWVDFVGq1AJ5Ql6ZXYKQAPL7-RB2IrQ+HNc6AYGLRyRWKAHwqKRSRoAPQA-Coa-L4KUYqAMDhc4arSabGaDZbjWL+sA6ABrB0tN0ewhwM77Mee73JjIAWTKTZbrOjsZi8agSb9eFTcAbK9bPYL1kLqlLFcrQA

SchmellaKaa commented 2 months ago

Sorry for the late answer... The response kinda flew over my head. I did it this way to only have to define the desired fields once...

for selects i need an object like:

export const minimalUserSelect = {
    id: userTable.id,
    name: userTable.name,
    // no password
    // ...
};

to use it like this:

const users = await db.select(minimalUserSelect).from(userTable)

and for queries i need another object with boolean values for the columns... There i would have to define them again, but this time with:

export const minimalUserQueryColumns = {
    id: true,
    name: true
    // ...
}

i got around this using:

export const minimalUserQueryColumns = Object.keys(minimalUserSelect).reduce(
    (acc, key) => Object.assign(acc, { [key]: true }),
    {} as Record<keyof typeof minimalUserSelect, true>
);

if i use userSelectSchema.pick() or userSelectSchema.omit() i need to define the same fields as defined in the minimalUserSelect again...

This is not really a problem if you omit only one field but once multiple fields are picked/omitted defining them in two places is not very maintainable over a larger codebase... A single definition object for this is really useful, however, this Object.keys() and Object.assign() workaround is not really beautiful...