Skip to content

Commit 611967d

Browse files
authored
Merge branch 'master' into make-v27
2 parents 0ca06cd + 44c76f3 commit 611967d

File tree

10 files changed

+359
-315
lines changed

10 files changed

+359
-315
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@
3333

3434
## Version 26
3535

36+
### v26.3.0
37+
38+
- Supporting several latest features of Zod 4.2 and 4.3 by the `Integration` generator:
39+
- `z.looseRecord()` schema;
40+
- Object properties wrapped by `z.exactOptional()` or created by `ZodType::exactOptional()` method.
41+
3642
### v26.2.0
3743

3844
- Ability to specify a custom name for a schema in the generated Documentation:

example/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"express-zod-api": "workspace:*",
1818
"http-errors": "catalog:dev",
1919
"qs": "^6.14.1",
20-
"swagger-ui-express": "^5.0.0",
20+
"swagger-ui-express": "^5.0.1",
2121
"typescript": "catalog:dev",
2222
"zod": "catalog:dev"
2323
}

express-zod-api/package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "express-zod-api",
3-
"version": "26.2.0",
3+
"version": "26.3.0",
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": {
@@ -43,7 +43,7 @@
4343
},
4444
"dependencies": {
4545
"@express-zod-api/zod-plugin": "workspace:^",
46-
"ansis": "^4.1.0",
46+
"ansis": "^4.2.0",
4747
"node-mocks-http": "^1.17.2",
4848
"openapi3-ts": "^4.5.0",
4949
"ramda": "catalog:prod"
@@ -90,7 +90,7 @@
9090
"@types/express": "catalog:dev",
9191
"@types/express-fileupload": "catalog:dev",
9292
"@types/http-errors": "catalog:dev",
93-
"@types/node-forge": "^1.3.11",
93+
"@types/node-forge": "^1.3.14",
9494
"@types/ramda": "catalog:dev",
9595
"camelize-ts": "catalog:dev",
9696
"compression": "catalog:dev",
@@ -99,10 +99,10 @@
9999
"express": "catalog:dev",
100100
"express-fileupload": "catalog:dev",
101101
"http-errors": "catalog:dev",
102-
"node-forge": "^1.3.2",
102+
"node-forge": "^1.3.3",
103103
"snakify-ts": "^2.3.0",
104104
"typescript": "catalog:dev",
105-
"undici": "^7.10.0",
105+
"undici": "^7.16.0",
106106
"zod": "catalog:dev"
107107
},
108108
"keywords": [

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -187,9 +187,15 @@ export class TypescriptAPI {
187187
value: Typeable,
188188
{
189189
isOptional,
190+
hasUndefined = isOptional,
190191
isDeprecated,
191192
comment,
192-
}: { isOptional?: boolean; isDeprecated?: boolean; comment?: string } = {},
193+
}: {
194+
isOptional?: boolean;
195+
hasUndefined?: boolean;
196+
isDeprecated?: boolean;
197+
comment?: string;
198+
} = {},
193199
) => {
194200
const propType = this.ensureTypeNode(value);
195201
const node = this.f.createPropertySignature(
@@ -198,7 +204,7 @@ export class TypescriptAPI {
198204
isOptional
199205
? this.f.createToken(this.ts.SyntaxKind.QuestionToken)
200206
: undefined,
201-
isOptional
207+
hasUndefined
202208
? this.makeUnion([
203209
propType,
204210
this.ensureTypeNode(this.ts.SyntaxKind.UndefinedKeyword),

express-zod-api/src/zts.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,15 @@ const onObject: Producer = (
7676
([key, value]) => {
7777
const { description: comment, deprecated: isDeprecated } =
7878
globalRegistry.get(value) || {};
79+
const isOptional =
80+
(isResponse ? value._zod.optout : value._zod.optin) === "optional";
81+
const hasUndefined =
82+
isOptional && !(value instanceof z.core.$ZodExactOptional);
7983
return api.makeInterfaceProp(key, next(value), {
8084
comment,
8185
isDeprecated,
82-
isOptional:
83-
(isResponse ? value._zod.optout : value._zod.optin) === "optional",
86+
isOptional,
87+
hasUndefined,
8488
});
8589
},
8690
);
@@ -124,7 +128,16 @@ const onTuple: Producer = (
124128
const onRecord: Producer = (
125129
{ _zod: { def } }: z.core.$ZodRecord,
126130
{ next, api },
127-
) => api.ensureTypeNode("Record", [def.keyType, def.valueType].map(next));
131+
) => {
132+
const [keyNode, valueNode] = [def.keyType, def.valueType].map(next);
133+
const primary = api.ensureTypeNode("Record", [keyNode, valueNode]);
134+
const isLoose = def.mode === "loose";
135+
if (!isLoose) return primary;
136+
return api.f.createIntersectionTypeNode([
137+
primary,
138+
api.ensureTypeNode("Record", ["PropertyKey", valueNode]),
139+
]);
140+
};
128141

129142
const intersect = R.tryCatch(
130143
(api: TypescriptAPI, nodes: ts.TypeNode[]) => {
@@ -170,7 +183,8 @@ const onWrapped: Producer = (
170183
| z.core.$ZodCatch
171184
| z.core.$ZodDefault
172185
| z.core.$ZodOptional
173-
| z.core.$ZodNonOptional,
186+
| z.core.$ZodNonOptional
187+
| z.core.$ZodExactOptional,
174188
{ next },
175189
) => next(def.innerType);
176190

express-zod-api/tests/__snapshots__/zts.spec.ts.snap

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ exports[`zod-to-ts > Example > should produce the expected results 1`] = `
5353
] | bigint)[];
5454
};
5555
}>;
56+
looseRecord: Record<"one" | "two", boolean> & Record<PropertyKey, boolean>;
5657
map: any;
5758
set: any;
5859
intersection: (string & number) | bigint;
@@ -76,6 +77,13 @@ exports[`zod-to-ts > Example > should produce the expected results 1`] = `
7677
catch: number;
7778
pipeline: string;
7879
readonly: string;
80+
extended: {
81+
hex: string;
82+
hash: string;
83+
};
84+
codec: string;
85+
slug: string;
86+
xor: string | number;
7987
}"
8088
`;
8189

@@ -283,6 +291,7 @@ exports[`zod-to-ts > z.optional() > Zod 4: should add question mark only to opti
283291
required: string;
284292
}
285293
] | undefined;
294+
exact?: string;
286295
}"
287296
`;
288297

express-zod-api/tests/zts.spec.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ describe("zod-to-ts", () => {
150150
}),
151151
}),
152152
),
153+
looseRecord: z.looseRecord(z.literal(["one", "two"]), z.boolean()),
153154
map: z.map(z.string(), z.array(z.object({ string: z.string() }))),
154155
set: z.set(z.string()),
155156
intersection: z.intersection(z.string(), z.number()).or(z.bigint()),
@@ -171,6 +172,13 @@ describe("zod-to-ts", () => {
171172
catch: z.number().catch(123),
172173
pipeline: z.string().regex(/\d+/).transform(Number).pipe(z.number()),
173174
readonly: z.string().readonly(),
175+
extended: z.object({}).extend({ hex: z.hex(), hash: z.hash("sha256") }),
176+
codec: z.codec(z.string(), z.number(), {
177+
encode: String,
178+
decode: Number,
179+
}),
180+
slug: z.string().slugify(),
181+
xor: z.xor([z.string(), z.number()]),
174182
});
175183

176184
test("should produce the expected results", () => {
@@ -199,6 +207,7 @@ describe("zod-to-ts", () => {
199207
}),
200208
])
201209
.optional(),
210+
exact: z.string().exactOptional(),
202211
});
203212

204213
test("Zod 4: does not add undefined to it, unwrap as is", () => {

package.json

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,19 @@
1515
},
1616
"devDependencies": {
1717
"@arethetypeswrong/core": "^0.18.2",
18-
"@tsconfig/node20": "^20.1.5",
19-
"@types/node": "^24.0.0",
20-
"@vitest/coverage-v8": "^4.0.1",
21-
"eslint": "^9.28.0",
22-
"eslint-config-prettier": "^10.1.5",
23-
"eslint-plugin-allowed-dependencies": "^2.0.0",
24-
"eslint-plugin-prettier": "^5.4.1",
25-
"husky": "^9.0.5",
18+
"@tsconfig/node20": "^20.1.8",
19+
"@types/node": "^24.10.4",
20+
"@vitest/coverage-v8": "^4.0.16",
21+
"eslint": "^9.39.2",
22+
"eslint-config-prettier": "^10.1.8",
23+
"eslint-plugin-allowed-dependencies": "^2.0.1",
24+
"eslint-plugin-prettier": "^5.5.4",
25+
"husky": "^9.1.7",
2626
"prettier": "3.7.4",
27-
"tsdown": "^0.18.0",
28-
"tsx": "^4.19.4",
27+
"tsdown": "^0.18.4",
28+
"tsx": "^4.21.0",
2929
"typescript-eslint": "catalog:dev",
30-
"vitest": "^4.0.1"
30+
"vitest": "^4.0.16"
3131
},
3232
"packageManager": "[email protected]+sha512.72d699da16b1179c14ba9e64dc71c9a40988cbdc65c264cb0e489db7de917f20dcf4d64d8723625f2969ba52d4b7e2a1170682d9ac2a5dcaeaab732b7e16f04a"
3333
}

0 commit comments

Comments
 (0)