Skip to content

Commit 644f037

Browse files
committed
improve cross-version zod support
1 parent 62d44fb commit 644f037

63 files changed

Lines changed: 459 additions & 251 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.changeset/zod-4-support.md

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

12-
Add zod v4 support. You can now use zod 3.25+ or zod 4 (the minimum zod 3 version is now 3.25.0). `zod` is now a peer dependency of `@trigger.dev/core`, `@trigger.dev/sdk`, `@trigger.dev/redis-worker`, and `@trigger.dev/schema-to-json`, so it shares a single copy with your project; if you depend on `@trigger.dev/core` directly, add `zod` to your own dependencies.
12+
Add Zod 4 support. You can now use Zod 3.25.76+ or Zod 4.
1313

14-
The published type declarations are built against zod 4. If you are still on zod 3, keep `skipLibCheck: true` in your tsconfig (the default in Trigger.dev's templates).
14+
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/build/package.json

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,7 @@
9292
"esbuild": "^0.23.0",
9393
"rimraf": "6.0.1",
9494
"tshy": "^3.0.2",
95-
"tsx": "4.17.0",
96-
"zod": "4.4.3"
97-
},
98-
"peerDependencies": {
99-
"zod": "^3.25.0 || ^4.0.0"
95+
"tsx": "4.17.0"
10096
},
10197
"engines": {
10298
"node": ">=18.20.0"

packages/core/package.json

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@
225225
"std-env": "^3.8.1",
226226
"tinyexec": "^0.3.2",
227227
"uncrypto": "^0.1.3",
228-
"zod-error": "2.0.0",
228+
"zod": "^3.25.76 || ^4.0.0",
229229
"zod-validation-error": "^5.0.0"
230230
},
231231
"devDependencies": {
@@ -244,8 +244,7 @@
244244
"superjson": "^2.2.1",
245245
"ts-essentials": "10.0.1",
246246
"tshy": "^3.0.2",
247-
"tsx": "4.17.0",
248-
"zod": "4.4.3"
247+
"tsx": "4.17.0"
249248
},
250249
"engines": {
251250
"node": ">=18.20.0"
@@ -665,6 +664,6 @@
665664
"types": "./dist/commonjs/index.d.ts",
666665
"module": "./dist/esm/index.js",
667666
"peerDependencies": {
668-
"zod": "^3.25.0 || ^4.0.0"
667+
"zod": "^3.25.76 || ^4.0.0"
669668
}
670669
}

packages/core/src/schemas/eventFilter.ts

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

33
export const stringPatternMatchers = [
44
z.object({

packages/core/src/schemas/json.ts

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

33
const LiteralSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]);
44
type Literal = z.infer<typeof LiteralSchema>;

packages/core/src/v3/apiClient/core.ts

Lines changed: 77 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { z } from "zod";
2-
import { fromZodError, ValidationError } from "zod-validation-error";
1+
import { z as z3 } from "zod/v3";
2+
import { z } from "zod/v4";
3+
import { fromZodError, ValidationError } from "zod-validation-error/v4";
34
import type { RetryOptions } from "../schemas/index.js";
45
import { calculateNextRetryDelay } from "../utils/retries.js";
56
import { ApiConnectionError, ApiError, ApiSchemaValidationError } from "./errors.js";
@@ -12,6 +13,10 @@ import { SemanticInternalAttributes } from "../semanticInternalAttributes.js";
1213
import type { TriggerTracer } from "../tracer.js";
1314
import { randomUUID } from "../utils/crypto.js";
1415
import { accessoryAttributes } from "../utils/styleAttributes.js";
16+
import type {
17+
AnyZodSchema,
18+
inferZodSchemaOutput,
19+
} from "../types/schemas.js";
1520
import type {
1621
CursorPageParams,
1722
CursorPageResponse,
@@ -71,16 +76,16 @@ interface FetchOffsetLimitPageParams extends OffsetLimitPageParams {
7176
query?: URLSearchParams;
7277
}
7378

74-
export function zodfetch<TResponseBodySchema extends z.ZodTypeAny>(
79+
export function zodfetch<TResponseBodySchema extends AnyZodSchema>(
7580
schema: TResponseBodySchema,
7681
url: string,
7782
requestInit?: RequestInit,
78-
options?: ZodFetchOptions<z.output<TResponseBodySchema>>
79-
): ApiPromise<z.output<TResponseBodySchema>> {
83+
options?: ZodFetchOptions<inferZodSchemaOutput<TResponseBodySchema>>
84+
): ApiPromise<inferZodSchemaOutput<TResponseBodySchema>> {
8085
return new ApiPromise(_doZodFetch(schema, url, requestInit, options));
8186
}
8287

83-
export function zodfetchCursorPage<TItemSchema extends z.ZodTypeAny>(
88+
export function zodfetchCursorPage<TItemSchema extends AnyZodSchema>(
8489
schema: TItemSchema,
8590
url: string,
8691
params: FetchCursorPageParams,
@@ -101,13 +106,21 @@ export function zodfetchCursorPage<TItemSchema extends z.ZodTypeAny>(
101106
query.set("page[before]", params.before);
102107
}
103108

104-
const cursorPageSchema = z.object({
105-
data: z.array(schema),
106-
pagination: z.object({
107-
next: z.string().optional(),
108-
previous: z.string().optional(),
109-
}),
110-
});
109+
const cursorPageSchema = "_zod" in schema
110+
? z.object({
111+
data: z.array(schema as any),
112+
pagination: z.object({
113+
next: z.string().optional(),
114+
previous: z.string().optional(),
115+
}),
116+
})
117+
: z3.object({
118+
data: z3.array(schema as any),
119+
pagination: z3.object({
120+
next: z3.string().optional(),
121+
previous: z3.string().optional(),
122+
}),
123+
});
111124

112125
const $url = new URL(url);
113126
$url.search = query.toString();
@@ -117,7 +130,7 @@ export function zodfetchCursorPage<TItemSchema extends z.ZodTypeAny>(
117130
return new CursorPagePromise(fetchResult, schema, url, params, requestInit, options);
118131
}
119132

120-
export function zodfetchOffsetLimitPage<TItemSchema extends z.ZodTypeAny>(
133+
export function zodfetchOffsetLimitPage<TItemSchema extends AnyZodSchema>(
121134
schema: TItemSchema,
122135
url: string,
123136
params: FetchOffsetLimitPageParams,
@@ -134,22 +147,33 @@ export function zodfetchOffsetLimitPage<TItemSchema extends z.ZodTypeAny>(
134147
query.set("page", String(params.page));
135148
}
136149

137-
const offsetLimitPageSchema = z.object({
138-
data: z.array(schema),
139-
pagination: z.object({
140-
currentPage: z.coerce.number(),
141-
totalPages: z.coerce.number(),
142-
count: z.coerce.number(),
143-
}),
144-
});
150+
const offsetLimitPageSchema = "_zod" in schema
151+
? z.object({
152+
data: z.array(schema as any),
153+
pagination: z.object({
154+
currentPage: z.coerce.number(),
155+
totalPages: z.coerce.number(),
156+
count: z.coerce.number(),
157+
}),
158+
})
159+
: z3.object({
160+
data: z3.array(schema as any),
161+
pagination: z3.object({
162+
currentPage: z3.coerce.number(),
163+
totalPages: z3.coerce.number(),
164+
count: z3.coerce.number(),
165+
}),
166+
});
145167

146168
const $url = new URL(url);
147169
$url.search = query.toString();
148170

149171
const fetchResult = _doZodFetch(offsetLimitPageSchema, $url.href, requestInit, options);
150172

151173
return new OffsetLimitPagePromise(
152-
fetchResult as Promise<ZodFetchResult<OffsetLimitPageResponse<z.output<TItemSchema>>>>,
174+
fetchResult as Promise<
175+
ZodFetchResult<OffsetLimitPageResponse<inferZodSchemaOutput<TItemSchema>>>
176+
>,
153177
schema,
154178
url,
155179
params,
@@ -195,12 +219,12 @@ async function traceZodFetch<T>(
195219
);
196220
}
197221

198-
async function _doZodFetch<TResponseBodySchema extends z.ZodTypeAny>(
222+
async function _doZodFetch<TResponseBodySchema extends AnyZodSchema>(
199223
schema: TResponseBodySchema,
200224
url: string,
201225
requestInit?: PromiseOrValue<RequestInit>,
202-
options?: ZodFetchOptions<z.output<TResponseBodySchema>>
203-
): Promise<ZodFetchResult<z.output<TResponseBodySchema>>> {
226+
options?: ZodFetchOptions<inferZodSchemaOutput<TResponseBodySchema>>
227+
): Promise<ZodFetchResult<inferZodSchemaOutput<TResponseBodySchema>>> {
204228
let $requestInit = await requestInit;
205229

206230
return traceZodFetch({ url, requestInit: $requestInit, options }, async (span) => {
@@ -223,13 +247,13 @@ async function _doZodFetch<TResponseBodySchema extends z.ZodTypeAny>(
223247
});
224248
}
225249

226-
async function _doZodFetchWithRetries<TResponseBodySchema extends z.ZodTypeAny>(
250+
async function _doZodFetchWithRetries<TResponseBodySchema extends AnyZodSchema>(
227251
schema: TResponseBodySchema,
228252
url: string,
229253
requestInit?: RequestInit,
230254
options?: ZodFetchOptions,
231255
attempt = 1
232-
): Promise<ZodFetchResult<z.output<TResponseBodySchema>>> {
256+
): Promise<ZodFetchResult<inferZodSchemaOutput<TResponseBodySchema>>> {
233257
try {
234258
const response = await context.with(suppressTracing(context.active()), () =>
235259
fetch(url, requestInitWithCache(requestInit))
@@ -470,12 +494,12 @@ export class ApiPromise<T> extends Promise<T> {
470494
}
471495
}
472496

473-
export class CursorPagePromise<TItemSchema extends z.ZodTypeAny>
474-
extends ApiPromise<CursorPage<z.output<TItemSchema>>>
475-
implements AsyncIterable<z.output<TItemSchema>>
497+
export class CursorPagePromise<TItemSchema extends AnyZodSchema>
498+
extends ApiPromise<CursorPage<inferZodSchemaOutput<TItemSchema>>>
499+
implements AsyncIterable<inferZodSchemaOutput<TItemSchema>>
476500
{
477501
constructor(
478-
result: Promise<ZodFetchResult<CursorPageResponse<z.output<TItemSchema>>>>,
502+
result: Promise<ZodFetchResult<CursorPageResponse<inferZodSchemaOutput<TItemSchema>>>>,
479503
private schema: TItemSchema,
480504
private url: string,
481505
private params: FetchCursorPageParams,
@@ -490,7 +514,9 @@ export class CursorPagePromise<TItemSchema extends z.ZodTypeAny>
490514
);
491515
}
492516

493-
#fetchPage(params: Omit<CursorPageParams, "limit">): Promise<CursorPage<z.output<TItemSchema>>> {
517+
#fetchPage(
518+
params: Omit<CursorPageParams, "limit">
519+
): Promise<CursorPage<inferZodSchemaOutput<TItemSchema>>> {
494520
return zodfetchCursorPage(
495521
this.schema,
496522
this.url,
@@ -515,12 +541,12 @@ export class CursorPagePromise<TItemSchema extends z.ZodTypeAny>
515541
}
516542
}
517543

518-
export class OffsetLimitPagePromise<TItemSchema extends z.ZodTypeAny>
519-
extends ApiPromise<OffsetLimitPage<z.output<TItemSchema>>>
520-
implements AsyncIterable<z.output<TItemSchema>>
544+
export class OffsetLimitPagePromise<TItemSchema extends AnyZodSchema>
545+
extends ApiPromise<OffsetLimitPage<inferZodSchemaOutput<TItemSchema>>>
546+
implements AsyncIterable<inferZodSchemaOutput<TItemSchema>>
521547
{
522548
constructor(
523-
result: Promise<ZodFetchResult<OffsetLimitPageResponse<z.output<TItemSchema>>>>,
549+
result: Promise<ZodFetchResult<OffsetLimitPageResponse<inferZodSchemaOutput<TItemSchema>>>>,
524550
private schema: TItemSchema,
525551
private url: string,
526552
private params: FetchOffsetLimitPageParams,
@@ -541,7 +567,7 @@ export class OffsetLimitPagePromise<TItemSchema extends z.ZodTypeAny>
541567

542568
#fetchPage(
543569
params: Omit<FetchOffsetLimitPageParams, "limit">
544-
): Promise<OffsetLimitPage<z.output<TItemSchema>>> {
570+
): Promise<OffsetLimitPage<inferZodSchemaOutput<TItemSchema>>> {
545571
return zodfetchOffsetLimitPage(
546572
this.schema,
547573
this.url,
@@ -644,17 +670,18 @@ function injectRequestIdempotencyKey(
644670
};
645671
}
646672

647-
export type ZodFetchSSEMessageValueSchema<
648-
TDiscriminatedUnion extends z.ZodDiscriminatedUnion<any, any>,
649-
> = z.ZodFirstPartySchemaTypes | TDiscriminatedUnion;
673+
export type ZodFetchSSEMessageValueSchema<TSchema extends AnyZodSchema = AnyZodSchema> =
674+
TSchema;
650675

651676
export interface ZodFetchSSEMessageCatalogSchema {
652-
[key: string]: ZodFetchSSEMessageValueSchema<any>;
677+
[key: string]: AnyZodSchema;
653678
}
654679

655680
export type ZodFetchSSEMessageHandlers<TCatalogSchema extends ZodFetchSSEMessageCatalogSchema> =
656681
Partial<{
657-
[K in keyof TCatalogSchema]: (payload: z.infer<TCatalogSchema[K]>) => Promise<void> | void;
682+
[K in keyof TCatalogSchema]: (
683+
payload: inferZodSchemaOutput<TCatalogSchema[K]>
684+
) => Promise<void> | void;
658685
}>;
659686

660687
export type ZodFetchSSEOptions<TMessageCatalog extends ZodFetchSSEMessageCatalogSchema> = {
@@ -699,6 +726,10 @@ export class ZodFetchSSEResult<TMessageCatalog extends ZodFetchSSEMessageCatalog
699726

700727
const schema = this.options.messages[type];
701728

729+
if (!schema) {
730+
return;
731+
}
732+
702733
const result = schema.safeParse(payload);
703734

704735
if (result.success) {
@@ -734,12 +765,12 @@ export type ApiResult<TSuccessResult> =
734765
statusCode?: number;
735766
};
736767

737-
export async function wrapZodFetch<T extends z.ZodTypeAny>(
768+
export async function wrapZodFetch<T extends AnyZodSchema>(
738769
schema: T,
739770
url: string,
740771
requestInit?: RequestInit,
741-
options?: ZodFetchOptions<z.output<T>>
742-
): Promise<ApiResult<z.infer<T>>> {
772+
options?: ZodFetchOptions<inferZodSchemaOutput<T>>
773+
): Promise<ApiResult<inferZodSchemaOutput<T>>> {
743774
try {
744775
const response = await zodfetch(schema, url, requestInit, {
745776
retry: {

packages/core/src/v3/apiClient/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { nanoid } from "nanoid";
2-
import { z } from "zod";
2+
import { z } from "zod/v4";
33
import { VERSION } from "../../version.js";
44
import { isAdditionalApiKey } from "../apiKeys.js";
55
import type { ApiClientConfiguration } from "../apiClientManager-api.js";

packages/core/src/v3/apiClient/stream.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import type { z } from "zod";
1+
import type {
2+
AnyZodSchema,
3+
inferZodSchemaOutput,
4+
} from "../types/schemas.js";
25
import {
36
type Offset,
47
FetchError,
@@ -22,12 +25,12 @@ export type ZodShapeStreamOptions = {
2225
onError?: (e: Error) => void;
2326
};
2427

25-
export type ZodShapeStreamInstance<TShapeSchema extends z.ZodTypeAny> = {
26-
stream: AsyncIterableStream<z.output<TShapeSchema>>;
28+
export type ZodShapeStreamInstance<TShapeSchema extends AnyZodSchema> = {
29+
stream: AsyncIterableStream<inferZodSchemaOutput<TShapeSchema>>;
2730
stop: (delay?: number) => void;
2831
};
2932

30-
export function zodShapeStream<TShapeSchema extends z.ZodTypeAny>(
33+
export function zodShapeStream<TShapeSchema extends AnyZodSchema>(
3134
schema: TShapeSchema,
3235
url: string,
3336
options?: ZodShapeStreamOptions
@@ -59,7 +62,7 @@ export function zodShapeStream<TShapeSchema extends z.ZodTypeAny>(
5962
const readableShape = new ReadableShapeStream(shapeStream);
6063

6164
const stream = readableShape.stream.pipeThrough(
62-
new TransformStream<unknown, z.output<TShapeSchema>>({
65+
new TransformStream<unknown, inferZodSchemaOutput<TShapeSchema>>({
6366
async transform(chunk, controller) {
6467
const result = schema.safeParse(chunk);
6568

@@ -73,7 +76,7 @@ export function zodShapeStream<TShapeSchema extends z.ZodTypeAny>(
7376
);
7477

7578
return {
76-
stream: stream as AsyncIterableStream<z.output<TShapeSchema>>,
79+
stream: stream as AsyncIterableStream<inferZodSchemaOutput<TShapeSchema>>,
7780
stop: (delay?: number) => {
7881
if (delay) {
7982
setTimeout(() => {

0 commit comments

Comments
 (0)