Skip to content

Commit 58c0d05

Browse files
authored
Merge branch 'make-v24' into feat-error-tree
2 parents 4563bc9 + c1d875b commit 58c0d05

15 files changed

Lines changed: 111 additions & 73 deletions

CHANGELOG.md

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
- Switched to Zod 4:
88
- Minimum supported version of `zod` is 3.25.1, BUT imports MUST be from `zod/v4`;
9-
- Explanation of the versioning strategy: https://github.com/colinhacks/zod/issues/4371;
9+
- Read the [Explanation of the versioning strategy](https://github.com/colinhacks/zod/issues/4371);
1010
- Express Zod API, however, is not aiming to support both Zod 3 and Zod 4 simultaneously due to:
1111
- incompatibility of data structures;
1212
- operating composite schemas (need to avoid mixing schemas of different versions);
@@ -21,19 +21,23 @@
2121
- In order to specify an example for an input schema the `.example()` method must be called before `.transform()`;
2222
- The transforming proprietary schemas `ez.dateIn()` and `ez.dateOut()` now accept metadata as its argument:
2323
- This allows to set examples before transformation (`ez.dateIn()`) and to avoid the examples "branding";
24-
- Generating Documentation is mostly delegated to Zod 4 `z.toJSONSchema()`:
25-
- The basic depiction of each schema is now natively performed by Zod 4;
24+
- Changes to `Documentation`:
25+
- Generating Documentation is mostly delegated to Zod 4 `z.toJSONSchema()`;
2626
- Express Zod API implements some overrides and improvements to fit it into OpenAPI 3.1 that extends JSON Schema;
2727
- The `numericRange` option removed from `Documentation` class constructor argument;
2828
- The `Depicter` type signature changed: became a postprocessing function returning an overridden JSON Schema;
29-
- The `optionalPropStyle` option removed from `Integration` class constructor:
29+
- Changes to `Integration`:
30+
- The `optionalPropStyle` option removed from `Integration` class constructor:
3031
- Use `.optional()` to add question mark to the object property as well as `undefined` to its type;
3132
- Use `.or(z.undefined())` to add `undefined` to the type of the object property;
32-
- Reasoning: https://x.com/colinhacks/status/1919292504861491252;
33-
- `z.any()` and `z.unknown()` are not optional, details: https://v4.zod.dev/v4/changelog#changes-zunknown-optionality.
34-
- The argument of `ResultHandler::handler` is now discriminated: either `output` or `error` is null, not both;
33+
- See the [reasoning](https://x.com/colinhacks/status/1919292504861491252);
34+
- `z.any()` and `z.unknown()` are required: [details](https://v4.zod.dev/v4/changelog#changes-zunknown-optionality);
35+
- Added types generation for `z.never()`, `z.void()` and `z.unknown()` schemas;
36+
- The fallback type for unsupported schemas and unclear transformations in response changed from `any` to `unknown`;
37+
- The argument of `ResultHandler::handler` is now discriminated: either `output` or `error` is `null`, not both;
3538
- The `getExamples()` public helper removed — use `.meta()?.examples` instead;
36-
- The `ez.file()` schema removed: use `z.string()`, `z.base64()` or the new `ez.buffer()` instead;
39+
- Added the new proprietary schema `ez.buffer()`;
40+
- The `ez.file()` schema removed: use `z.string()`, `z.base64()`, `ez.buffer()` or their union;
3741
- Consider the automated migration using the built-in ESLint rule.
3842

3943
```js

eslint.config.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,14 @@ const importConcerns = [
2020
"ImportDeclaration[source.value='ramda'] > ImportDefaultSpecifier",
2121
message: "use import * as R from 'ramda'",
2222
},
23+
{
24+
selector: "ImportDeclaration[source.value=/^zod/] > ImportDefaultSpecifier",
25+
message: "do import { z } instead",
26+
},
27+
{
28+
selector: "ImportDeclaration[source.value='zod'] > ImportSpecifier",
29+
message: "should import from zod/v4", // @todo remove when zod version changed to 4.0.0
30+
},
2331
...builtinModules.map((mod) => ({
2432
selector: `ImportDeclaration[source.value='${mod}']`,
2533
message: `use node:${mod} for the built-in module`,

example/endpoints/list-users.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import z from "zod/v4";
1+
import { z } from "zod/v4";
22
import { arrayRespondingFactory } from "../factories";
33

44
/**

express-zod-api/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "express-zod-api",
3-
"version": "24.0.0-beta.8",
3+
"version": "24.0.0-beta.9",
44
"description": "A Typescript framework to help you get an API server up and running with I/O schema validation and custom middlewares in minutes.",
55
"license": "MIT",
66
"repository": {

express-zod-api/src/common-helpers.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { AuxMethod, Method } from "./method";
88

99
/** @desc this type does not allow props assignment, but it works for reading them when merged with another interface */
1010
export type EmptyObject = z.output<EmptySchema>;
11+
/** Avoiding z.ZodObject<Record<string, never>, $strip>, because its z.output<> is generic "object" (external issue) */
1112
export type EmptySchema = z.ZodRecord<z.ZodString, z.ZodNever>;
1213
export type FlatObject = Record<string, unknown>;
1314

@@ -64,8 +65,8 @@ export const getInput = (
6465
export const ensureError = (subject: unknown): Error =>
6566
subject instanceof Error
6667
? subject
67-
: subject instanceof z.ZodError
68-
? new z.ZodRealError(subject.issues) // ZodError is not an instance of Error, unlike ZodRealError that is
68+
: subject instanceof z.ZodError // ZodError does not extend Error, unlike ZodRealError that does
69+
? new z.ZodRealError(subject.issues)
6970
: new Error(String(subject));
7071

7172
export const getMessageFromError = (error: Error): string => {

express-zod-api/src/date-out-schema.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,7 @@ export const dateOut = ({
1212
.brand(ezDateOutBrand as symbol)
1313
.meta({
1414
...rest,
15-
examples: examples as Array<string & z.$brand> | undefined,
15+
examples: examples as
16+
| Array<NonNullable<typeof examples>[number] & z.$brand>
17+
| undefined,
1618
});

express-zod-api/src/endpoints-factory.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ export class EndpointsFactory<
118118
}
119119

120120
public build<BOUT extends IOSchema, BIN extends IOSchema = EmptySchema>({
121-
input = z.object({}) as IOSchema as BIN, // @todo revisit
121+
input = z.object({}) as unknown as BIN,
122122
output: outputSchema,
123123
operationId,
124124
scope,

express-zod-api/src/json-schema-helpers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const isJsonObjectSchema = (
99
const propsMerger = R.mergeDeepWith((a: unknown, b: unknown) => {
1010
if (Array.isArray(a) && Array.isArray(b)) return R.concat(a, b);
1111
if (a === b) return b;
12-
throw new Error("Can not flatten properties");
12+
throw new Error("Can not flatten properties", { cause: { a, b } });
1313
});
1414

1515
const canMerge = R.pipe(

express-zod-api/src/middleware.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export class Middleware<
5050
readonly #handler: Handler<z.output<IN>, OPT, OUT>;
5151

5252
constructor({
53-
input = z.object({}) as IOSchema as IN, // @todo revisit
53+
input = z.object({}) as unknown as IN,
5454
security,
5555
handler,
5656
}: {

express-zod-api/src/zod-plugin.ts

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -141,14 +141,12 @@ if (!(metaSymbol in globalThis)) {
141141
if (typeof Cls !== "function") continue;
142142
Object.defineProperties(Cls.prototype, {
143143
["example" satisfies keyof z.ZodType]: {
144-
get(): z.ZodType["example"] {
145-
return exampleSetter.bind(this);
146-
},
144+
value: exampleSetter,
145+
writable: false,
147146
},
148147
["deprecated" satisfies keyof z.ZodType]: {
149-
get(): z.ZodType["deprecated"] {
150-
return deprecationSetter.bind(this);
151-
},
148+
value: deprecationSetter,
149+
writable: false,
152150
},
153151
["brand" satisfies keyof z.ZodType]: {
154152
set() {}, // this is required to override the existing method
@@ -162,19 +160,11 @@ if (!(metaSymbol in globalThis)) {
162160
Object.defineProperty(
163161
z.ZodDefault.prototype,
164162
"label" satisfies keyof z.ZodDefault,
165-
{
166-
get(): z.ZodDefault["label"] {
167-
return labelSetter.bind(this);
168-
},
169-
},
163+
{ value: labelSetter, writable: false },
170164
);
171165
Object.defineProperty(
172166
z.ZodObject.prototype,
173167
"remap" satisfies keyof z.ZodObject,
174-
{
175-
get() {
176-
return objectMapper.bind(this);
177-
},
178-
},
168+
{ value: objectMapper, writable: false },
179169
);
180170
}

0 commit comments

Comments
 (0)