Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 976b436

Browse files
authored
v4.1.6 (#5222)
* Prevent undefined in .default(). Fixes #5213 * Properly implement async codecs. Fixes #5217 * Bump version * Update stubs * Improve stubs
1 parent 937f73c commit 976b436

File tree

10 files changed

+176
-26
lines changed

10 files changed

+176
-26
lines changed

packages/zod/jsr.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@zod/zod",
3-
"version": "4.1.5",
3+
"version": "4.1.6",
44
"exports": {
55
"./package.json": "./package.json",
66
".": "./src/index.ts",

packages/zod/package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "zod",
3-
"version": "4.1.5",
3+
"version": "4.1.6",
44
"type": "module",
55
"license": "MIT",
66
"author": "Colin McDonnell <[email protected]>",
@@ -18,7 +18,8 @@
1818
"**/*.cjs",
1919
"**/*.d.ts",
2020
"**/*.d.mts",
21-
"**/*.d.cts"
21+
"**/*.d.cts",
22+
"**/package.json"
2223
],
2324
"keywords": [
2425
"typescript",
@@ -126,7 +127,7 @@
126127
"scripts": {
127128
"clean": "git clean -xdf . -e node_modules",
128129
"build": "zshy --project tsconfig.build.json",
129-
"postbuild": "pnpm biome check --write .",
130+
"postbuild": "tsx ../../scripts/write-stub-package-jsons.ts && pnpm biome check --write .",
130131
"test:watch": "pnpm vitest",
131132
"test": "pnpm vitest run",
132133
"prepublishOnly": "tsx ../../scripts/check-versions.ts"

packages/zod/src/v4/classic/schemas.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ export interface ZodType<
8989
nonoptional(params?: string | core.$ZodNonOptionalParams): ZodNonOptional<this>;
9090
nullable(): ZodNullable<this>;
9191
nullish(): ZodOptional<ZodNullable<this>>;
92-
default(def: core.output<this>): ZodDefault<this>;
92+
default(def: util.NoUndefined<core.output<this>>): ZodDefault<this>;
9393
default(def: () => util.NoUndefined<core.output<this>>): ZodDefault<this>;
9494
prefault(def: () => core.input<this>): ZodPrefault<this>;
9595
prefault(def: core.input<this>): ZodPrefault<this>;
@@ -1931,8 +1931,8 @@ export function codec<const A extends core.SomeType, B extends core.SomeType = c
19311931
in_: A,
19321932
out: B,
19331933
params: {
1934-
decode: (value: core.output<A>, payload: core.ParsePayload<core.output<A>>) => core.input<B>;
1935-
encode: (value: core.input<B>, payload: core.ParsePayload<core.input<B>>) => core.output<A>;
1934+
decode: (value: core.output<A>, payload: core.ParsePayload<core.output<A>>) => core.util.MaybeAsync<core.input<B>>;
1935+
encode: (value: core.input<B>, payload: core.ParsePayload<core.input<B>>) => core.util.MaybeAsync<core.output<A>>;
19361936
}
19371937
): ZodCodec<A, B> {
19381938
return new ZodCodec({

packages/zod/src/v4/classic/tests/codec.test.ts

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -414,11 +414,11 @@ test("codec type enforcement - correct encode/decode signatures", () => {
414414
encode: (value: number) => String(value), // core.input<B> -> core.output<A>
415415
});
416416

417-
// These should compile without errors - correct types
418-
expectTypeOf<(value: string, payload: z.core.ParsePayload<string>) => number>(
417+
// These should compile without errors - correct types (async support)
418+
expectTypeOf<(value: string, payload: z.core.ParsePayload<string>) => z.core.util.MaybeAsync<number>>(
419419
stringToNumberCodec.def.transform
420420
).toBeFunction();
421-
expectTypeOf<(value: number, payload: z.core.ParsePayload<number>) => string>(
421+
expectTypeOf<(value: number, payload: z.core.ParsePayload<number>) => z.core.util.MaybeAsync<string>>(
422422
stringToNumberCodec.def.reverseTransform
423423
).toBeFunction();
424424

@@ -468,11 +468,11 @@ test("codec type enforcement - complex types", () => {
468468
}
469469
);
470470

471-
// Verify correct types are inferred
472-
expectTypeOf<(input: UserInput, payload: z.core.ParsePayload<UserInput>) => User>(
471+
// Verify correct types are inferred (async support)
472+
expectTypeOf<(input: UserInput, payload: z.core.ParsePayload<UserInput>) => z.core.util.MaybeAsync<User>>(
473473
userCodec.def.transform
474474
).toBeFunction();
475-
expectTypeOf<(user: User, payload: z.core.ParsePayload<User>) => UserInput>(
475+
expectTypeOf<(user: User, payload: z.core.ParsePayload<User>) => z.core.util.MaybeAsync<UserInput>>(
476476
userCodec.def.reverseTransform
477477
).toBeFunction();
478478

@@ -530,3 +530,33 @@ test("codex with overwrites", () => {
530530
}
531531
`);
532532
});
533+
534+
test("async codec functionality", async () => {
535+
// Test that async encode/decode functions work properly
536+
const asyncCodec = z.codec(z.string(), z.number(), {
537+
decode: async (str) => {
538+
await new Promise((resolve) => setTimeout(resolve, 1)); // Simulate async work
539+
return Number.parseFloat(str);
540+
},
541+
encode: async (num) => {
542+
await new Promise((resolve) => setTimeout(resolve, 1)); // Simulate async work
543+
return num.toString();
544+
},
545+
});
546+
547+
// Test async decode/encode
548+
const decoded = await z.decodeAsync(asyncCodec, "42.5");
549+
expect(decoded).toBe(42.5);
550+
551+
const encoded = await z.encodeAsync(asyncCodec, 42.5);
552+
expect(encoded).toBe("42.5");
553+
554+
// Test that both sync and async work
555+
const mixedCodec = z.codec(z.string(), z.number(), {
556+
decode: async (str) => Number.parseFloat(str),
557+
encode: (num) => num.toString(), // sync encode
558+
});
559+
560+
const mixedResult = await z.decodeAsync(mixedCodec, "123");
561+
expect(mixedResult).toBe(123);
562+
});

packages/zod/src/v4/core/schemas.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3593,9 +3593,9 @@ export interface $ZodPipeDef<A extends SomeType = $ZodType, B extends SomeType =
35933593
in: A;
35943594
out: B;
35953595
/** Only defined inside $ZodCodec instances. */
3596-
transform?: (value: core.output<A>, payload: ParsePayload<core.output<A>>) => core.input<B>;
3596+
transform?: (value: core.output<A>, payload: ParsePayload<core.output<A>>) => util.MaybeAsync<core.input<B>>;
35973597
/** Only defined inside $ZodCodec instances. */
3598-
reverseTransform?: (value: core.input<B>, payload: ParsePayload<core.input<B>>) => core.output<A>;
3598+
reverseTransform?: (value: core.input<B>, payload: ParsePayload<core.input<B>>) => util.MaybeAsync<core.output<A>>;
35993599
}
36003600

36013601
export interface $ZodPipeInternals<A extends SomeType = $ZodType, B extends SomeType = $ZodType>
@@ -3653,8 +3653,8 @@ function handlePipeResult(left: ParsePayload, next: $ZodType, ctx: ParseContextI
36533653
////////////////////////////////////////////
36543654
////////////////////////////////////////////
36553655
export interface $ZodCodecDef<A extends SomeType = $ZodType, B extends SomeType = $ZodType> extends $ZodPipeDef<A, B> {
3656-
transform: (value: core.output<A>, payload: ParsePayload<core.output<A>>) => core.input<B>;
3657-
reverseTransform: (value: core.input<B>, payload: ParsePayload<core.input<B>>) => core.output<A>;
3656+
transform: (value: core.output<A>, payload: ParsePayload<core.output<A>>) => util.MaybeAsync<core.input<B>>;
3657+
reverseTransform: (value: core.input<B>, payload: ParsePayload<core.input<B>>) => util.MaybeAsync<core.output<A>>;
36583658
}
36593659

36603660
export interface $ZodCodecInternals<A extends SomeType = $ZodType, B extends SomeType = $ZodType>
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
export const version = {
22
major: 4,
33
minor: 1,
4-
patch: 5 as number,
4+
patch: 6 as number,
55
} as const;

packages/zod/src/v4/mini/schemas.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1460,8 +1460,8 @@ export function codec<const A extends SomeType, B extends core.SomeType = core.$
14601460
in_: A,
14611461
out: B,
14621462
params: {
1463-
decode: (value: core.output<A>, payload: core.ParsePayload<core.output<A>>) => core.input<B>;
1464-
encode: (value: core.input<B>, payload: core.ParsePayload<core.input<B>>) => core.output<A>;
1463+
decode: (value: core.output<A>, payload: core.ParsePayload<core.output<A>>) => core.util.MaybeAsync<core.input<B>>;
1464+
encode: (value: core.input<B>, payload: core.ParsePayload<core.input<B>>) => core.util.MaybeAsync<core.output<A>>;
14651465
}
14661466
): ZodMiniCodec<A, B> {
14671467
return new ZodMiniCodec({

packages/zod/src/v4/mini/tests/codec.test.ts

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -409,11 +409,11 @@ test("codec type enforcement - correct encode/decode signatures", () => {
409409
encode: (value: number) => String(value), // core.input<B> -> core.output<A>
410410
});
411411

412-
// These should compile without errors - correct types
413-
expectTypeOf<(value: string, payload: z.core.ParsePayload<string>) => number>(
412+
// These should compile without errors - correct types (async support)
413+
expectTypeOf<(value: string, payload: z.core.ParsePayload<string>) => z.core.util.MaybeAsync<number>>(
414414
stringToNumberCodec.def.transform
415415
).toBeFunction();
416-
expectTypeOf<(value: number, payload: z.core.ParsePayload<number>) => string>(
416+
expectTypeOf<(value: number, payload: z.core.ParsePayload<number>) => z.core.util.MaybeAsync<string>>(
417417
stringToNumberCodec.def.reverseTransform
418418
).toBeFunction();
419419

@@ -450,6 +450,36 @@ test("codec type enforcement - correct encode/decode signatures", () => {
450450
});
451451
});
452452

453+
test("async codec functionality", async () => {
454+
// Test that async encode/decode functions work properly
455+
const asyncCodec = z.codec(z.string(), z.number(), {
456+
decode: async (str) => {
457+
await new Promise((resolve) => setTimeout(resolve, 1)); // Simulate async work
458+
return Number.parseFloat(str);
459+
},
460+
encode: async (num) => {
461+
await new Promise((resolve) => setTimeout(resolve, 1)); // Simulate async work
462+
return num.toString();
463+
},
464+
});
465+
466+
// Test async decode/encode
467+
const decoded = await z.decodeAsync(asyncCodec, "42.5");
468+
expect(decoded).toBe(42.5);
469+
470+
const encoded = await z.encodeAsync(asyncCodec, 42.5);
471+
expect(encoded).toBe("42.5");
472+
473+
// Test that both sync and async work
474+
const mixedCodec = z.codec(z.string(), z.number(), {
475+
decode: async (str) => Number.parseFloat(str),
476+
encode: (num) => num.toString(), // sync encode
477+
});
478+
479+
const mixedResult = await z.decodeAsync(mixedCodec, "123");
480+
expect(mixedResult).toBe(123);
481+
});
482+
453483
test("codec type enforcement - complex types", () => {
454484
type User = { id: number; name: string };
455485
type UserInput = { id: string; name: string };
@@ -463,11 +493,11 @@ test("codec type enforcement - complex types", () => {
463493
}
464494
);
465495

466-
// Verify correct types are inferred
467-
expectTypeOf<(input: UserInput, payload: z.core.ParsePayload<UserInput>) => User>(
496+
// Verify correct types are inferred (async support)
497+
expectTypeOf<(input: UserInput, payload: z.core.ParsePayload<UserInput>) => z.core.util.MaybeAsync<User>>(
468498
userCodec.def.transform
469499
).toBeFunction();
470-
expectTypeOf<(user: User, payload: z.core.ParsePayload<User>) => UserInput>(
500+
expectTypeOf<(user: User, payload: z.core.ParsePayload<User>) => z.core.util.MaybeAsync<UserInput>>(
471501
userCodec.def.reverseTransform
472502
).toBeFunction();
473503

play.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import * as z from "zod";
2+
3+
z;
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
#!/usr/bin/env tsx
2+
3+
import { readdirSync, statSync, writeFileSync } from "node:fs";
4+
import { join } from "node:path";
5+
6+
const STUB_PACKAGE_JSON_CONTENT = `{
7+
"type": "module",
8+
"main": "./index.cjs",
9+
"module": "./index.js",
10+
"types": "./index.d.cts"
11+
}
12+
`;
13+
14+
/**
15+
* Recursively find all index.d.cts files in a directory
16+
*/
17+
function findIndexJsFiles(dir: string, relativePath = ""): string[] {
18+
const results: string[] = [];
19+
20+
try {
21+
const entries = readdirSync(dir);
22+
23+
for (const entry of entries) {
24+
// Skip node_modules
25+
if (entry !== "node_modules") {
26+
const fullPath = join(dir, entry);
27+
const relativeFilePath = relativePath ? join(relativePath, entry) : entry;
28+
29+
try {
30+
const stat = statSync(fullPath);
31+
32+
if (stat.isDirectory()) {
33+
// Recursively search subdirectories
34+
results.push(...findIndexJsFiles(fullPath, relativeFilePath));
35+
} else if (entry === "index.d.cts") {
36+
// Found an index.d.cts file
37+
results.push(relativePath || ".");
38+
}
39+
} catch {
40+
// Skip files/directories we can't access
41+
}
42+
}
43+
}
44+
} catch {
45+
// Skip directories we can't read
46+
}
47+
48+
return results;
49+
}
50+
51+
/**
52+
* Script to write stub package.json files to directories containing index.d.cts files.
53+
* This resolves TypeScript assignability issues by providing explicit type declarations.
54+
*/
55+
function writeStubPackageJsons() {
56+
const zodPackageRoot = join(import.meta.dirname, "../packages/zod");
57+
58+
// Find all directories containing index.d.cts files
59+
const dirsWithIndexJs = findIndexJsFiles(zodPackageRoot);
60+
61+
const processedDirs = new Set<string>();
62+
63+
for (const dir of dirsWithIndexJs) {
64+
// Skip root directory (it already has a proper package.json)
65+
if (dir === ".") {
66+
continue;
67+
}
68+
69+
// Avoid duplicate processing
70+
if (processedDirs.has(dir)) {
71+
continue;
72+
}
73+
processedDirs.add(dir);
74+
75+
const packageJsonPath = join(zodPackageRoot, dir, "package.json");
76+
77+
// Write the stub package.json
78+
writeFileSync(packageJsonPath, STUB_PACKAGE_JSON_CONTENT, "utf8");
79+
console.log(`✅ Created ${dir}/package.json`);
80+
}
81+
82+
console.log("\n✨ Done! All stub package.json files have been created.");
83+
}
84+
85+
// Run the script
86+
writeStubPackageJsons();

0 commit comments

Comments
 (0)