Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@
### v28.0.0

- Supported Node.js versions: `^22.19.0 || ^24.0.0`;
- Zod compatibility: `^4.3.4` (supports Zod 4.4+ without upper limit);
- The Zod plugin is no longer installed automatically — it's an optional peer dependency now:
- To keep using `.example()`, `.label()`, `.remap()`, `.deprecated()` and `.brand()` methods on schemas
install the `@express-zod-api/zod-plugin` manually and import it (ideally at the top of a file declaring `Routing`);
- To keep using `.example()`, `.label()`, `.remap()`, `.deprecated()` and methods on schemas, as well as runtime
Comment thread
pullfrog[bot] marked this conversation as resolved.
distinguisable brands, install the `@express-zod-api/zod-plugin` manually and import it (ideally at the top of a
file declaring your `Routing`);
- Breaking change: `ZodType::brand()` method is no longer patched by the plugin:
- Use `.xBrand()` method instead alias for `.meta({ "x-brand": ... })` and does not conflict with Zod 4.4;
Comment thread
pullfrog[bot] marked this conversation as resolved.
Outdated
- Breaking changes to the `createConfig()` argument (object):
- property `wrongMethodBehavior` (number) changed to `hintAllowedMethods` (boolean);
- property `methodLikeRouteBehavior` (string literal) changed to `recognizeMethodDependentRoutes` (boolean);
Expand Down
7 changes: 3 additions & 4 deletions example/example.documentation.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ paths:
features:
type: array
items:
$ref: "#/components/schemas/Feature"
$ref: "#/components/schemas/Schema1"
required:
- id
- name
Expand Down Expand Up @@ -1040,19 +1040,18 @@ paths:
description: HEAD /v2/users/list Negative response
components:
schemas:
Feature:
Schema1:
type: object
properties:
title:
type: string
features:
type: array
items:
$ref: "#/components/schemas/Feature"
$ref: "#/components/schemas/Schema1"
required:
- title
additionalProperties: false
id: Feature
responses: {}
parameters: {}
examples: {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1804,7 +1804,7 @@ paths:
cuid:
type: string
format: cuid
pattern: ^[cC][^\\s-]{8,}$
pattern: ^[cC][0-9a-z]{6,}$
cuid2:
type: string
format: cuid2
Expand Down Expand Up @@ -4974,7 +4974,7 @@ paths:
required: true
description: POST /v1/:name Parameter
schema:
$ref: "#/components/schemas/NameParam"
$ref: "#/components/schemas/Schema1"
Comment thread
RobinTail marked this conversation as resolved.
Outdated
requestBody:
description: POST /v1/:name Request body
content:
Expand Down Expand Up @@ -5036,13 +5036,12 @@ paths:
message: Sample error message
components:
schemas:
NameParam:
Schema1:
anyOf:
- type: string
const: John
- type: string
const: Jane
id: NameParam
responses: {}
parameters: {}
examples: {}
Expand Down
8 changes: 0 additions & 8 deletions express-zod-api/tests/__snapshots__/env.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -286,14 +286,6 @@ exports[`Environment checks > Zod new features > input examples of transformatio
}
`;

exports[`Environment checks > Zod new features > meta id goes directly to depiction 1`] = `
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"id": "uniq",
"type": "string",
}
`;

exports[`Environment checks > Zod new features > meta() merge, not just overrides 1`] = `
{
"description": "some",
Expand Down
8 changes: 4 additions & 4 deletions express-zod-api/tests/__snapshots__/zts.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ exports[`zod-to-ts > Example > should produce the expected results 1`] = `
enum: "hi" | "bye";
intersectionWithTransform: (number & bigint) & (number & string);
date: any;
undefined?: undefined;
undefined: undefined;
null: null;
void: undefined;
any: any;
Expand Down Expand Up @@ -153,7 +153,7 @@ exports[`zod-to-ts > PrimitiveSchema (isResponse=false) > outputs correct typesc
number: number;
boolean: boolean;
date: any;
undefined?: undefined;
undefined: undefined;
null: null;
void: undefined;
any: any;
Expand All @@ -168,7 +168,7 @@ exports[`zod-to-ts > PrimitiveSchema (isResponse=true) > outputs correct typescr
number: number;
boolean: boolean;
date: unknown;
undefined?: undefined;
undefined: undefined;
null: null;
void: undefined;
any: any;
Expand Down Expand Up @@ -242,7 +242,7 @@ exports[`zod-to-ts > z.object() > escapes correctly 1`] = `
$e: any;
"4t": any;
_r: any;
"-r"?: undefined;
"-r": undefined;
}"
`;

Expand Down
6 changes: 4 additions & 2 deletions express-zod-api/tests/env.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,8 +194,10 @@ describe("Environment checks", () => {
},
);

test("meta id goes directly to depiction", () => {
expect(z.toJSONSchema(z.string().meta({ id: "uniq" }))).toMatchSnapshot();
test("meta id does NOT go intto depiction", () => {
expect(
Comment thread
pullfrog[bot] marked this conversation as resolved.
z.toJSONSchema(z.string().meta({ id: "uniq" })),
).not.toHaveProperty("id");
});
});

Expand Down
22 changes: 11 additions & 11 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ catalogs:
prod:
"ramda": "^0.32.0"
peer:
"zod": "~4.3.4"
"zod": "^4.3.4"
dev:
"@types/compression": "^1.8.1"
"@types/express": "^5.0.6"
Expand All @@ -64,7 +64,7 @@ catalogs:
"http-errors": "^2.0.1"
"typescript": "^6.0.2"
"typescript-eslint": "^8.59.0"
"zod": "~4.3.4"
"zod": "^4.4.1"
overrides:
"@scarf/scarf": "-"
"lightningcss": "-"
5 changes: 5 additions & 0 deletions zod-plugin/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
- Supported Node.js versions: `^22.19.0 || ^24.0.0`;
- `getBrand()` removed:
- use `schema.meta()?.["x-brand"]` instead.
- Added `xBrand()` method to all Zod schemas:
- shorthand for `.meta({ "x-brand": ... })`;
- This method does not conflict with Zod 4.4+ internal mechanisms;
- Use `xBrand()` instead of `brand()` for setting runtime-distinguishable brands;
- Zod compatibility updated to `^4.3.4` (supports Zod 4.4+).

## Version 4

Expand Down
10 changes: 5 additions & 5 deletions zod-plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,26 @@ This module extends Zod functionality when it's imported:
- shorthand for `.meta({ examples: [...] })`;
- Adds `.deprecated()` method to all Zod schemas:
- shorthand for `.meta({ deprecated: true })`;
- Adds `.xBrand()` method to all Zod schemas:
- shorthand for `.meta({ "x-brand": ... })` making the brand available in runtime;
- This method does not conflict with Zod 4.4+ internal mechanisms;
- Adds `.label()` method to `ZodDefault`:
- shorthand for `.meta({ default: ... })`;
- Adds `.remap()` method to `ZodObject` for renaming object properties:
- Supports a mapping object or an object transforming function as an argument;
- Relies on `R.renameKeys()` from the `ramda` library;
- Alters the `.brand()` method on all Zod schemas:
- shorthand for `.meta({ "x-brand": ... })` making the brand available in runtime;

## Requirements

- Compatible with Zod versions `~4.3.4` (<4.4.0);
- Zod 4.4+ support will be available in v5 (the next major version).
- Compatible with Zod versions `^4.3.4` (including 4.4+);

## Basic usage

```ts
import { z } from "zod";
import "@express-zod-api/zod-plugin";

const schema = z.string().example("test").example("another").brand("custom");
const schema = z.string().example("test").example("another").xBrand("custom");

schema.meta(); // { examples: ["test", "another"], "x-brand": "custom" }
```
5 changes: 4 additions & 1 deletion zod-plugin/src/augmentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ declare module "zod/v4/core" {
* @see https://github.com/colinhacks/zod/blob/90efe7fa6135119224412c7081bd12ef0bccef26/plugin/effect/src/index.ts#L21-L31
* @desc This code modifies and extends zod's functionality immediately when importing the plugin.
* @desc Enables .example() and .deprecated() on all schemas (ZodType)
* @desc Enables .xBrand() on all schemas as an alternative to .brand() that doesn't conflict with Zod 4.4+
* @desc Enables .label() on ZodDefault
* @desc Enables .remap() on ZodObject
* @desc Stores the argument supplied to .brand() on all schemas (runtime distinguishable branded types)
* @desc Stores the argument supplied to .xBrand() on all schemas (runtime distinguishable branded types)
Comment thread
RobinTail marked this conversation as resolved.
Outdated
* */
declare module "zod" {
interface ZodType<
Expand All @@ -29,6 +30,8 @@ declare module "zod" {
/** @desc Shorthand for .meta({ examples }) */
example(example: z.output<this>): this;
deprecated(): this;
/** @desc Shorthand for .meta({ "x-brand": ... }) */
xBrand(brand?: PropertyKey): this;
}
interface ZodDefault<T extends z.core.SomeType = z.core.$ZodType>
extends z._ZodType<z.core.$ZodDefaultInternals<T>>, z.core.$ZodDefault<T> {
Expand Down
8 changes: 3 additions & 5 deletions zod-plugin/src/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,9 @@ if (!(pluginFlag in globalThis)) {
value: deprecationSetter,
writable: false,
},
["brand" satisfies keyof z.ZodType]: {
set() {}, // this is required to override the existing method
get() {
return brandSetter.bind(this) as z.ZodType["brand"];
},
["xBrand" satisfies keyof z.ZodType]: {
value: brandSetter,
writable: false,
},
});
}
Expand Down
8 changes: 4 additions & 4 deletions zod-plugin/tests/runtime.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,23 +67,23 @@ describe.each<{ variant: string; z: typeof zESM }>([
});
});

describe(".brand()", () => {
describe(".xBrand()", () => {
test("should set the brand", () => {
const subject = z.string().brand("test");
const subject = z.string().xBrand("test");
expect(subject.meta()).toEqual({ "x-brand": "test" });
});

test("should withstand refinements", () => {
const schema = z.string();
const schemaWithMeta = schema.brand("test");
const schemaWithMeta = schema.xBrand("test");
expect(schemaWithMeta.meta()).toEqual({ "x-brand": "test" });
expect(schemaWithMeta.regex(/@example.com$/).meta()).toEqual({
"x-brand": "test",
});
});

test("should withstand describing", () => {
const schema = z.string().brand("test").describe("something");
const schema = z.string().xBrand("test").describe("something");
expect(schema.meta()).toEqual({
"x-brand": "test",
description: "something",
Expand Down
Loading