zod-openapi icon indicating copy to clipboard operation
zod-openapi copied to clipboard

Zod 4

Open samchungy opened this issue 8 months ago • 1 comments

https://v4.zod.dev/ecosystem

I am aware and will be seeing how we can migrate in an easy way :)

Probably will require:

  1. Add a codegen to migrate from .openapi() to .meta().
  2. Delete some schema generation internals

Some current issues I'm waiting on:

https://github.com/colinhacks/zod/issues/4089 https://github.com/colinhacks/zod/issues/4094 https://github.com/colinhacks/zod/issues/4134

PRs:

https://github.com/colinhacks/zod/pull/4091 https://github.com/colinhacks/zod/pull/4090

Please be patient, as this may take a little time to work through with Colin to make sure we get the result which will lead to the least changes for you.

If Zod 4's json schema generation doesn't reach what I deem an acceptable level of parity with this library, I'll keep .openapi() around for a little while longer and keep chipping away at adding features to the main zod lib.

samchungy avatar Apr 11 '25 01:04 samchungy

I think it would be great to see you guys adopt .meta() BUT I have nothing against the prototype/declare module approach in general. I think it's a totally valid way to extend Zod. It's very possible your existing users would prefer a seamless migration path to use Zod 4 without a big API change.

Very curious to hear any thoughts you have on v4 and any issues you encounter migrating your libraries! ❤️

PS left a comment here: https://github.com/colinhacks/zod/pull/4090


Linking to my PR so people can find this tracking issue from there: https://github.com/colinhacks/zod/pull/4074

colinhacks avatar Apr 12 '25 01:04 colinhacks

All PRs & issues have been closed on my end! Keep them coming!

colinhacks avatar May 05 '25 00:05 colinhacks

@colinhacks

I think it would be great to see you guys adopt .meta() BUT I have nothing against the prototype/declare module approach in general. I think it's a totally valid way to extend Zod. It's very possible your existing users would prefer a seamless migration path to use Zod 4 without a big API change.

I wouldn't encourage it. It seem to break integrations with other libraries, e.g., https://github.com/samchungy/zod-openapi/issues/441

punkpeye avatar May 06 '25 23:05 punkpeye

Thanks for your patience everyone, I should have something in a decent state by the start of next week.

samchungy avatar May 21 '25 13:05 samchungy

@samchungy I migrated earlier this week most parsers to accept both v3 and v4/core types (in fact I migrated most of the lib). I am not sure where/how you chose to implement support for v4, if it helps more than it adds overhead to you I would be happy to share.

ct-verio avatar May 22 '25 11:05 ct-verio

@ct-verio Can you please publish your changes on a fork? I'd love to try them out.

kingbri1 avatar May 22 '25 13:05 kingbri1

@ct-verio Can you please publish your changes on a fork? I'd love to try them out.

Yes, I'll fork it, need few hours though. It's not 100% and I could not figure out yet how to add the openapi() implementation to the v4 prototypes (given the core does not expose classes, rather constructor functions) - TS/JS is not my forte :) I was able to pass 100% of the tests for v3, but I can't run tests for v4 involving the openapi() extension yet

Few comments:

  • there is no more native enum in core - enum is using record type and for literal string arrays the key is the same as the value
  • check parameters are no longer directly present in the high-level type, I had to iterate through the checks collection and extract what was relevant.
  • to check if an object is of a certain type I used the "traits" array
  • to extract the actual name of the zod type I used ._zod.constr.name
  • I suffixed some of the v3 methods with V3, and left the original name for cross-compatible or v4-related methods

...one hour later :) I pushed my changes to zod-v4 branch on this fork: zod-openapi I noticed there's a breaking change since yesterday - I cannot run pnpm i or test anymore, it seems it has some issue with skuba/jest installation but I did not have enough time to dig. If I remove pnpm-workspace.yaml and install @types/jest I can build it though.

ct-verio avatar May 22 '25 14:05 ct-verio

Does the new version use meta()?

punkpeye avatar May 22 '25 19:05 punkpeye

Does the new version use meta()?

if you are asking about my fork, it does not - I tried to minimize the changes to the existing code, even though I tried initially to get rid of the extension. You could use meta as-is but then the existing merging logic down the chain will break, or anchor zodOpenApi / openapi inside metadata - I thought the decision should be left to @samchungy & others who have a more informed say. This one does not even have the extension logic working yet, only the parsers are updated.

ct-verio avatar May 22 '25 19:05 ct-verio

Does the new version use meta()?

In my current iteration, yes. Which is unfortunately a breaking change but means we hopefully can remove the side effects that this library currently introduces via the extend import/function call

I'm trying to also provide a code-mod to do most of the migration for all of you.

samchungy avatar May 23 '25 02:05 samchungy

Does the new version use meta()?

In my current iteration, yes. Which is unfortunately a breaking change but means we hopefully can remove the side effects that this library currently introduces via the extend import/function call

I'm trying to also provide a code-mod to do most of the migration for all of you.

That's awesome. That's preferable

punkpeye avatar May 23 '25 14:05 punkpeye

@samchungy Is there any timeline for adding support for zod/v4 in zod-openapi? I just wanted to make sure before updating to Zod v4 to avoid any breaking changes.

JkNPatel avatar May 29 '25 16:05 JkNPatel

@samchungy Is there any timeline for adding support for zod/v4 in zod-openapi? I just wanted to make sure before updating to Zod v4 to avoid any breaking changes.

I was hoping to have it mostly complete by this week, but I've been running into some integration issues. I've been mostly linking the issues blocking/slowing me down in the OP.

Mind you, I don't have the same ability as Collin to work on this full-time, so it's mostly me plugging away at it after work or during my own time.

samchungy avatar May 30 '25 01:05 samchungy

Would anybody be angry if I dropped support for < OpenAPI 3.1.0?

It's proving challenging to support both at the moment.

samchungy avatar Jun 09 '25 03:06 samchungy

Not at all

punkpeye avatar Jun 09 '25 07:06 punkpeye

Would anybody be angry if I dropped support for < OpenAPI 3.1.0?

Nope

JkNPatel avatar Jun 09 '25 11:06 JkNPatel

Would anybody be angry if I dropped support for < OpenAPI 3.1.0?

It's proving challenging to support both at the moment.

You could even only support >= 3.1.1 https://github.com/OAI/OpenAPI-Specification/releases/tag/3.1.1

arjunyel avatar Jun 09 '25 16:06 arjunyel

Okay hey everyone, sorry for the wait. This ended up being essentially a complete rewrite.

I expect to do a full release sometime this week. I'm yet to write a codegen to help people migrate but if you want to give the new version a shot It's available on npm via zod-openapi@beta

Please note: The CHANGELOG is fairly substantial but you should be able to migrate following this guide: https://github.com/samchungy/zod-openapi/blob/v4-stash/docs/v5.md

samchungy avatar Jun 30 '25 11:06 samchungy

@samchungy Thank you so much! I am using it immediately for my work project and it is working well✨

This is a minor request, but it might be more tree-shaking friendly to import with wildcard.

import * as z from 'zod/v4';

As an example, tree-shaking the following code with tsup resulted in a significant change in file size.

import * as z from 'zod/v4';
// import { z } from 'zod/v4';

const schema = z.string();
console.log(schema);
  • wildcard: 81.18 KB
  • no wildcard: 383.91 KB

This may not be very important since the product is basically used on the server side, but we would appreciate your consideration.

mdoi2 avatar Jul 02 '25 05:07 mdoi2

Can I suggest to add a utility that allows to type guard definitions?

Example:

This:

const RepositoryModelZodSchema = z.object({
  url: z.url().meta({
    description: 'URL to the repository hosting the source code',
    example: 'https://github.com/pskill9/hn-server',
  }),
});

Would become:

const RepositoryModelZodSchema = z.object({
  url: z.url().meta(zodOpenApiMeta({
    description: 'URL to the repository hosting the source code',
    example: 'https://github.com/pskill9/hn-server',
  })),
});

where zodOpenApiMeta is simply a function that tests the types:

const zodOpenApiMeta = (meta: ZodOpenApiMeta) => {
  return meta;
}

This would help prevent accidentally introducing unknown properties.

punkpeye avatar Jul 02 '25 17:07 punkpeye

@samchungy does https://github.com/samchungy/fastify-zod-openapi need to be updated to be compatible with v5?

punkpeye avatar Jul 02 '25 17:07 punkpeye

@punkpeye

Can I suggest to add a utility that allows to type guard definitions?

The description and ~~example~~ are already defined in the GlobalMeta interface of zod/v4. So it should be type safe without doing anything, but won't it work? →PS: The example type seems to have been removed in the latest release(v3.25.68).

And, zod-openapi's own properties are defined by extending GlobalMeta interface, so you can resolve the type by either importing zod-openapi or including d.ts containing /// <reference types="zod-openapi" /> in tsconfig to include it, of these will resolve the type.

https://github.com/samchungy/zod-openapi/blob/beta/src/types.ts#L11

mdoi2 avatar Jul 02 '25 17:07 mdoi2

@punkpeye

does https://github.com/samchungy/fastify-zod-openapi need to be updated to be compatible with v5?

A pull request has already been created. The status is still draft. https://github.com/samchungy/fastify-zod-openapi/pull/263

mdoi2 avatar Jul 02 '25 17:07 mdoi2

Can I suggest to add a utility that allows to type guard definitions?

Example:

This:

const RepositoryModelZodSchema = z.object({ url: z.url().meta({ description: 'URL to the repository hosting the source code', example: 'https://github.com/pskill9/hn-server', }), }); Would become:

const RepositoryModelZodSchema = z.object({ url: z.url().meta(zodOpenApiMeta({ description: 'URL to the repository hosting the source code', example: 'https://github.com/pskill9/hn-server', })), }); where zodOpenApiMeta is simply a function that tests the types:

const zodOpenApiMeta = (meta: ZodOpenApiMeta) => { return meta; } This would help prevent accidentally introducing unknown properties.

I could potentially export a type like ZodOpenApiMeta or maybe something shorter like Meta so you can go z.string().meta({ description: 'foo' } satisfies ZodOpenApiMeta); but what mdoi said is correct we do already extend the global types.

samchungy avatar Jul 02 '25 22:07 samchungy

... but what mdoi said is correct we do already extend the global types.

Please don't.

It is truly one of the worst anti-patterns. Importing a package should not under any circumstances magically change zod types across the entire project.

Just provide a utility or ZodOpenApiMeta that users themselves need to validate against.

punkpeye avatar Jul 03 '25 07:07 punkpeye

... but what mdoi said is correct we do already extend the global types.

Please don't.

It is truly one of the worst anti-patterns. Importing a package should not under any circumstances magically change zod types across the entire project.

Just provide a utility or ZodOpenApiMeta that users themselves need to validate against.

I think you may be misinterpreting this. We're not performing any monkey patching this time around with our import. It's purely a TypeScript type import so you can choose to keep this as a dev dependency.

It purely extends the .meta() TypeScript types with optional types to give you better type hints and means the dependency on this library can be removed at compile time.

Requiring a runtime function is both ugly and unnecessary. We can avoid this by using a satisfies type.

samchungy avatar Jul 03 '25 11:07 samchungy