Skip to content

Conversation

zanellig
Copy link

The handleColumns used in drizzle-zod now uses the zodInstance passed in to the factory options.

The previous implementation caused createSchemaFactory to return its methods using z.object(columnSchemas) ignoring the custom zod instance passed to the CreateSchemaFactoryOptions.

Btw, love the work!

@zanellig zanellig force-pushed the fix/use-zod-instance-from-factory-options branch 4 times, most recently from 756a75e to 65b104e Compare August 19, 2025 07:05
The `handleColumns` used in `drizzle-zod` now uses the `zodInstance` passed in to the `factory` options.

The previous implementation caused `createSchemaFactory` to return its methods using `z.object(columnSchemas)` ignoring the custom zod instance passed to the `CreateSchemaFactoryOptions`.
@maelp
Copy link

maelp commented Aug 19, 2025

Would there also be a way to allow "refinements" on createSelectSchema, createInsertSchema, createUpdateSchema to be shared?

eg we describe a field with zod schema refinements, open-api description, etc, and those are propagated to the corresponding schemas of createSelectSchema, createInsertSchema, createUpdateSchema

obviously it should take into account that, eg, fields that have a default value and are not nullable are always set in "selectSchema", but are optional in "createSchema", etc

@zanellig
Copy link
Author

Would there also be a way to allow "refinements" on createSelectSchema, createInsertSchema, createUpdateSchema to be shared?

I see what you're saying, but refinements should generally work well with the generated schemas as this change doesn't touch on the logic that applies refinements to them, and should only change the reference to the zod instance used when generating the z.object from the resulting schema...

I just saw that z.any() @ line 43 still uses the imported instance from zod/v4. Although I don't think that would impose an issue, I can change it to use the factory.zodInstance if present...

Move the `zod` instance reference to the top of the function and use it on undefined columns to convert them to `z.ZodAny`
This change only re-implements the schemas for `literalSchema`, `jsonSchema` and `bufferSchema` in `columnToSchema` to use the `factory?.zodInstance` passed in to the `columnToSchema` function
@zanellig zanellig changed the title Fix handleColumns use of factory.zodInstance Fix handleColumns usage of factory.zodInstance Aug 19, 2025
@zanellig
Copy link
Author

I've been testing the changes and they work in the practical sense. The types are wrong though and I can't seem to get autocomplete working from the zodInstance passed to the factory...

import { describe, it, expect } from "bun:test";
import { createSchemaFactory } from "drizzle-zod";
import { z } from "@hono/zod-openapi";
import { json, mysqlTable, varchar } from "drizzle-orm/mysql-core";

const { createInsertSchema, createSelectSchema, createUpdateSchema } =
  createSchemaFactory({
    zodInstance: z,
  });

const TestSchema = mysqlTable("test", {
  id: varchar({ length: 36 }).notNull(),
  json: json().notNull(),
});
const TestInsertSchema = createInsertSchema(TestSchema);
const TestSelectSchema = createSelectSchema(TestSchema);
const TestUpdateSchema = createUpdateSchema(TestSchema);

describe("createInsertSchema usage of the zod instance", () => {
  it("should have openapi method on TestInsertSchema", () => {
    expect(TestInsertSchema.openapi).toBeDefined();
    expect(typeof TestInsertSchema.openapi).toBe("function");
    expect(TestInsertSchema.openapi).toBeInstanceOf(Function);
  });
});

describe("createSelectSchema usage of the zod instance", () => {
  it("should have openapi method on TestSelectSchema", () => {
    expect(TestSelectSchema.openapi).toBeDefined();
    expect(typeof TestSelectSchema.openapi).toBe("function");
    expect(TestSelectSchema.openapi).toBeInstanceOf(Function);
    expect(TestSelectSchema.shape).toHaveProperty("id");
  });
});

describe("createUpdateSchema usage of the zod instance", () => {
  it("should have openapi method on TestUpdateSchema", () => {
    expect(TestUpdateSchema.openapi).toBeDefined();
    expect(typeof TestUpdateSchema.openapi).toBe("function");
    expect(TestUpdateSchema.openapi).toBeInstanceOf(Function);
    expect(TestUpdateSchema.shape).toHaveProperty("id");
  });
});

describe("schemas must be generated correctly", () => {
  it("should generate TestInsertSchema with correct properties", () => {
    expect(TestInsertSchema.shape).toHaveProperty("id");
    expect(TestInsertSchema.shape).toHaveProperty("json");
    expect(TestInsertSchema.shape.id).toBeInstanceOf(z.ZodString);
    expect(TestInsertSchema.shape.json).toBeInstanceOf(z.ZodType);
  });

  it("should generate TestSelectSchema with correct properties", () => {
    expect(TestSelectSchema.shape).toHaveProperty("id");
    expect(TestSelectSchema.shape).toHaveProperty("json");
    expect(TestSelectSchema.shape.id).toBeInstanceOf(z.ZodString);
    expect(TestSelectSchema.shape.json).toBeInstanceOf(z.ZodType);
  });

  it("should generate TestUpdateSchema with correct properties", () => {
    expect(TestUpdateSchema.shape).toHaveProperty("json");
    expect(TestUpdateSchema.shape.json).toBeInstanceOf(z.ZodType);
  });
});

Tests pass, as we are using the correct instance, but the typing issue persists, giving us:

Property 'openapi' does not exist on type 'BuildSchema<"update", { id: MySqlColumn<{ name: "id"; tableName: "test"; dataType: "string"; columnType: "MySqlVarChar"; data: string; driverParam: string | number; notNull: true; hasDefault: false; isPrimaryKey: false; ... 5 more ...; generated: undefined; }, {}, {}>; json: MySqlColumn<...>; }, undefined, true | .....'.ts(2339)

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

Successfully merging this pull request may close these issues.

2 participants