Skip to content

Commit dd43436

Browse files
committed
fix: support Zod 3.25.56 alongside Zod 4
Preserve retry defaults and JSON Schema optional fields with older Zod permalinks. Keep AI schema types optional for Zod-only consumers without losing tool payload inference.
1 parent 693e6ca commit dd43436

16 files changed

Lines changed: 188 additions & 46 deletions

File tree

‎.changeset/zod-4-support.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,6 @@
99
"@trigger.dev/schema-to-json": minor
1010
---
1111

12-
Add Zod 4 support. You can now use Zod 3.25.76+ or Zod 4.
12+
Add Zod 4 support. You can now use Zod 3.25.56+ or Zod 4.
1313

1414
Zod remains a runtime dependency of packages that execute schemas, so existing and new installations continue to receive it automatically. The matching peer dependency range allows package managers to reuse either a compatible Zod 3 or Zod 4 installation from your project.

‎packages/cli-v3/src/utilities/commandError.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { formatCommandError } from "./commandError.js";
33

44
const expectedMessage = `Your installed Zod version is not supported by Trigger.dev.
55
6-
Trigger.dev requires zod@^3.25.76 or zod@^4.0.0. Upgrade Zod, reinstall your dependencies, and try again.`;
6+
Trigger.dev requires zod@^3.25.56 or zod@^4.0.0. Upgrade Zod, reinstall your dependencies, and try again.`;
77

88
describe("formatCommandError", () => {
99
it("explains when the installed Zod package does not export zod/v4", () => {

‎packages/cli-v3/src/utilities/commandError.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
const UNSUPPORTED_ZOD_MESSAGE = `Your installed Zod version is not supported by Trigger.dev.
22
3-
Trigger.dev requires zod@^3.25.76 or zod@^4.0.0. Upgrade Zod, reinstall your dependencies, and try again.`;
3+
Trigger.dev requires zod@^3.25.56 or zod@^4.0.0. Upgrade Zod, reinstall your dependencies, and try again.`;
44

55
export function formatCommandError(error: unknown): string {
66
if (isUnsupportedZodError(error)) {

‎packages/core/package.json‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@
229229
"std-env": "^3.8.1",
230230
"tinyexec": "^0.3.2",
231231
"uncrypto": "^0.1.3",
232-
"zod": "^3.25.76 || ^4.0.0",
232+
"zod": "^3.25.56 || ^4.0.0",
233233
"zod-validation-error": "^5.0.0"
234234
},
235235
"devDependencies": {
@@ -240,6 +240,7 @@
240240
"@types/humanize-duration": "^3.27.1",
241241
"ai": "^6.0.0",
242242
"ai-v7": "npm:ai@7.0.0-canary.159",
243+
"zod-v3-floor": "npm:zod@3.25.56",
243244
"defu": "^6.1.4",
244245
"esbuild": "^0.23.0",
245246
"rimraf": "^6.0.1",
@@ -677,6 +678,6 @@
677678
"types": "./dist/commonjs/index.d.ts",
678679
"module": "./dist/esm/index.js",
679680
"peerDependencies": {
680-
"zod": "^3.25.76 || ^4.0.0"
681+
"zod": "^3.25.56 || ^4.0.0"
681682
}
682683
}

‎packages/core/src/v3/schemas/fetch.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ export const FetchRetryHeadersStrategy = z.object({
2525
"iso_8601_duration_openai_variant",
2626
])
2727
.default("unix_timestamp")
28-
.optional(),
28+
.optional()
29+
// Early Zod 4 permalinks skip defaults inside optional schemas.
30+
.overwrite((value) => value ?? "unix_timestamp"),
2931
});
3032

3133
export type FetchRetryHeadersStrategy = z.infer<typeof FetchRetryHeadersStrategy>;

‎packages/core/src/v3/schemas/schemaCompositionCompatibility.test.ts‎

Lines changed: 45 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,15 @@ const require = createRequire(import.meta.url);
88
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "../../..");
99

1010
describe("schema composition compatibility", () => {
11-
it("parses Zod 4 parent schemas when the root Zod export resolves to Zod 3", async () => {
12-
const result = await build({
13-
stdin: {
14-
contents: `
11+
it.each(["zod", "zod-v3-floor"])(
12+
"parses composed schemas with %s and a Zod 3 root",
13+
async (zodPackage) => {
14+
const result = await build({
15+
stdin: {
16+
contents: `
1517
import { ScheduleMetadata, WebhookMetadata } from "./src/v3/schemas/schemas.ts";
1618
import { WebhookResource } from "./src/v3/schemas/resources.ts";
19+
import { FetchRetryHeadersStrategy } from "./src/v3/schemas/fetch.ts";
1720
1821
const verifierArtifact = {
1922
kind: "preset",
@@ -23,6 +26,12 @@ describe("schema composition compatibility", () => {
2326
const routingTarget = { type: "task", taskId: "my-task" };
2427
2528
export const parsed = {
29+
retry: FetchRetryHeadersStrategy.parse({
30+
strategy: "headers",
31+
limitHeader: "x-limit",
32+
remainingHeader: "x-remaining",
33+
resetHeader: "x-reset",
34+
}),
2635
schedule: ScheduleMetadata.parse({
2736
cron: "0 0 * * *",
2837
timezone: "UTC",
@@ -43,32 +52,39 @@ describe("schema composition compatibility", () => {
4352
}),
4453
};
4554
`,
46-
loader: "ts",
47-
resolveDir: packageRoot,
48-
},
49-
bundle: true,
50-
format: "esm",
51-
platform: "node",
52-
target: "node20",
53-
write: false,
54-
plugins: [
55-
{
56-
name: "resolve-root-zod-to-v3",
57-
setup(build) {
58-
build.onResolve({ filter: /^zod$/ }, () => ({ path: require.resolve("zod/v3") }));
59-
},
55+
loader: "ts",
56+
resolveDir: packageRoot,
6057
},
61-
],
62-
});
58+
bundle: true,
59+
format: "esm",
60+
platform: "node",
61+
target: "node20",
62+
write: false,
63+
plugins: [
64+
{
65+
name: "resolve-root-zod-to-v3",
66+
setup(build) {
67+
build.onResolve({ filter: /^zod$/ }, () => ({
68+
path: require.resolve(`${zodPackage}/v3`),
69+
}));
70+
build.onResolve({ filter: /^zod\/v4$/ }, () => ({
71+
path: require.resolve(`${zodPackage}/v4`),
72+
}));
73+
},
74+
},
75+
],
76+
});
6377

64-
const bundledModule = await import(
65-
`data:text/javascript;base64,${Buffer.from(result.outputFiles[0]!.text).toString("base64")}`
66-
);
78+
const bundledModule = await import(
79+
`data:text/javascript;base64,${Buffer.from(result.outputFiles[0]!.text).toString("base64")}`
80+
);
6781

68-
expect(bundledModule.parsed).toMatchObject({
69-
schedule: { window: "10%" },
70-
metadata: { id: "my-webhook" },
71-
resource: { id: "my-webhook" },
72-
});
73-
});
82+
expect(bundledModule.parsed).toMatchObject({
83+
retry: { resetFormat: "unix_timestamp" },
84+
schedule: { window: "10%" },
85+
metadata: { id: "my-webhook" },
86+
resource: { id: "my-webhook" },
87+
});
88+
}
89+
);
7490
});

‎packages/core/src/v3/sessionStreams/chatSnapshot.ts‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,7 @@
1919

2020
import { z } from "zod/v4";
2121

22-
import type { UIMessage } from "ai";
23-
24-
export type ChatSnapshotV1<TUIMessage extends UIMessage = UIMessage> = {
22+
export type ChatSnapshotV1<TUIMessage = unknown> = {
2523
version: 1;
2624
savedAt: number;
2725
messages: TUIMessage[];
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { jsonSchema } from "ai";
2+
import { describe, expect, it } from "vitest";
3+
import { getSchemaParseFn } from "./schemas.js";
4+
import { convertToolParametersToSchema } from "./tools.js";
5+
6+
describe("AI tool parameter schemas", () => {
7+
it("uses the caller's validator and preserves validation failures", async () => {
8+
const parameters = jsonSchema<{ count: number }>(
9+
{ type: "object", properties: { count: { type: "number" } }, required: ["count"] },
10+
{
11+
validate(value) {
12+
if (
13+
typeof value === "object" &&
14+
value !== null &&
15+
"count" in value &&
16+
typeof value.count === "number"
17+
) {
18+
return { success: true, value: { count: value.count } };
19+
}
20+
return { success: false, error: new Error("count must be a number") };
21+
},
22+
}
23+
);
24+
const parse = getSchemaParseFn(convertToolParametersToSchema(parameters));
25+
26+
await expect(parse({ count: 3 })).resolves.toEqual({ count: 3 });
27+
await expect(parse({ count: "invalid" })).rejects.toThrow("count must be a number");
28+
});
29+
});

‎packages/core/src/v3/types/tools.ts‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
1-
import type { Schema as AISchema } from "ai";
21
import {
32
isSchemaZodEsque,
43
type AnyZodSchema,
54
type inferZodSchemaOutput,
65
type Schema,
76
} from "./schemas.js";
87

8+
type ValidationResult<T> = { success: true; value: T } | { success: false; error: Error };
9+
10+
// The structural contract keeps Zod-only consumers independent of the optional AI SDK.
11+
type AISchema<T> = {
12+
_type: T;
13+
readonly jsonSchema: unknown;
14+
readonly validate?: (value: unknown) => ValidationResult<T> | PromiseLike<ValidationResult<T>>;
15+
};
16+
917
export type ToolTaskParameters = AnyZodSchema | AISchema<any>;
1018

1119
export type inferToolParameters<PARAMETERS extends ToolTaskParameters> =

‎packages/redis-worker/package.json‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
"nanoid": "^5.1.16",
2828
"p-limit": "^6.2.0",
2929
"seedrandom": "^3.0.5",
30-
"zod": "^3.25.76 || ^4.0.0",
30+
"zod": "^3.25.56 || ^4.0.0",
3131
"cron-parser": "^4.9.0"
3232
},
3333
"devDependencies": {
@@ -54,6 +54,6 @@
5454
}
5555
},
5656
"peerDependencies": {
57-
"zod": "^3.25.76 || ^4.0.0"
57+
"zod": "^3.25.56 || ^4.0.0"
5858
}
5959
}

0 commit comments

Comments
 (0)