Skip to content

Commit 5e6e5df

Browse files
committed
improve cross-version zod support
1 parent 7fbe648 commit 5e6e5df

63 files changed

Lines changed: 459 additions & 243 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: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,13 +96,11 @@
9696
"tsx": "4.17.0",
9797
"typescript": "6.0.3",
9898
"typescript5": "npm:typescript@5.9.3",
99-
"typescript7": "npm:typescript@7.0.2",
100-
"zod": "4.4.3"
99+
"typescript7": "npm:typescript@7.0.2"
101100
},
102101
"peerDependencies": {
103102
"@typescript/typescript6": "^6.0.0",
104-
"typescript": ">=5.0.0",
105-
"zod": "^3.25.0 || ^4.0.0"
103+
"typescript": ">=5.0.0"
106104
},
107105
"peerDependenciesMeta": {
108106
"@typescript/typescript6": {

‎packages/core/package.json‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +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",
232233
"zod-validation-error": "^5.0.0"
233234
},
234235
"devDependencies": {
@@ -245,8 +246,7 @@
245246
"superjson": "^2.2.1",
246247
"ts-essentials": "10.0.1",
247248
"tshy": "^4.1.3",
248-
"tsx": "4.17.0",
249-
"zod": "4.4.3"
249+
"tsx": "4.17.0"
250250
},
251251
"engines": {
252252
"node": ">=18.20.0"
@@ -677,6 +677,6 @@
677677
"types": "./dist/commonjs/index.d.ts",
678678
"module": "./dist/esm/index.js",
679679
"peerDependencies": {
680-
"zod": "^3.25.0 || ^4.0.0"
680+
"zod": "^3.25.76 || ^4.0.0"
681681
}
682682
}

‎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 } 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))
@@ -467,12 +491,12 @@ export class ApiPromise<T> extends Promise<T> {
467491
}
468492
}
469493

470-
export class CursorPagePromise<TItemSchema extends z.ZodTypeAny>
471-
extends ApiPromise<CursorPage<z.output<TItemSchema>>>
472-
implements AsyncIterable<z.output<TItemSchema>>
494+
export class CursorPagePromise<TItemSchema extends AnyZodSchema>
495+
extends ApiPromise<CursorPage<inferZodSchemaOutput<TItemSchema>>>
496+
implements AsyncIterable<inferZodSchemaOutput<TItemSchema>>
473497
{
474498
constructor(
475-
result: Promise<ZodFetchResult<CursorPageResponse<z.output<TItemSchema>>>>,
499+
result: Promise<ZodFetchResult<CursorPageResponse<inferZodSchemaOutput<TItemSchema>>>>,
476500
private schema: TItemSchema,
477501
private url: string,
478502
private params: FetchCursorPageParams,
@@ -487,7 +511,9 @@ export class CursorPagePromise<TItemSchema extends z.ZodTypeAny>
487511
);
488512
}
489513

490-
#fetchPage(params: Omit<CursorPageParams, "limit">): Promise<CursorPage<z.output<TItemSchema>>> {
514+
#fetchPage(
515+
params: Omit<CursorPageParams, "limit">
516+
): Promise<CursorPage<inferZodSchemaOutput<TItemSchema>>> {
491517
return zodfetchCursorPage(
492518
this.schema,
493519
this.url,
@@ -512,12 +538,12 @@ export class CursorPagePromise<TItemSchema extends z.ZodTypeAny>
512538
}
513539
}
514540

515-
export class OffsetLimitPagePromise<TItemSchema extends z.ZodTypeAny>
516-
extends ApiPromise<OffsetLimitPage<z.output<TItemSchema>>>
517-
implements AsyncIterable<z.output<TItemSchema>>
541+
export class OffsetLimitPagePromise<TItemSchema extends AnyZodSchema>
542+
extends ApiPromise<OffsetLimitPage<inferZodSchemaOutput<TItemSchema>>>
543+
implements AsyncIterable<inferZodSchemaOutput<TItemSchema>>
518544
{
519545
constructor(
520-
result: Promise<ZodFetchResult<OffsetLimitPageResponse<z.output<TItemSchema>>>>,
546+
result: Promise<ZodFetchResult<OffsetLimitPageResponse<inferZodSchemaOutput<TItemSchema>>>>,
521547
private schema: TItemSchema,
522548
private url: string,
523549
private params: FetchOffsetLimitPageParams,
@@ -538,7 +564,7 @@ export class OffsetLimitPagePromise<TItemSchema extends z.ZodTypeAny>
538564

539565
#fetchPage(
540566
params: Omit<FetchOffsetLimitPageParams, "limit">
541-
): Promise<OffsetLimitPage<z.output<TItemSchema>>> {
567+
): Promise<OffsetLimitPage<inferZodSchemaOutput<TItemSchema>>> {
542568
return zodfetchOffsetLimitPage(
543569
this.schema,
544570
this.url,
@@ -643,17 +669,18 @@ function injectRequestIdempotencyKey(
643669
};
644670
}
645671

646-
export type ZodFetchSSEMessageValueSchema<
647-
TDiscriminatedUnion extends z.ZodDiscriminatedUnion<any, any>,
648-
> = z.ZodFirstPartySchemaTypes | TDiscriminatedUnion;
672+
export type ZodFetchSSEMessageValueSchema<TSchema extends AnyZodSchema = AnyZodSchema> =
673+
TSchema;
649674

650675
export interface ZodFetchSSEMessageCatalogSchema {
651-
[key: string]: ZodFetchSSEMessageValueSchema<any>;
676+
[key: string]: AnyZodSchema;
652677
}
653678

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

659686
export type ZodFetchSSEOptions<TMessageCatalog extends ZodFetchSSEMessageCatalogSchema> = {
@@ -698,6 +725,10 @@ export class ZodFetchSSEResult<TMessageCatalog extends ZodFetchSSEMessageCatalog
698725

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

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

703734
if (result.success) {
@@ -733,12 +764,12 @@ export type ApiResult<TSuccessResult> =
733764
statusCode?: number;
734765
};
735766

736-
export async function wrapZodFetch<T extends z.ZodTypeAny>(
767+
export async function wrapZodFetch<T extends AnyZodSchema>(
737768
schema: T,
738769
url: string,
739770
requestInit?: RequestInit,
740-
options?: ZodFetchOptions<z.output<T>>
741-
): Promise<ApiResult<z.infer<T>>> {
771+
options?: ZodFetchOptions<inferZodSchemaOutput<T>>
772+
): Promise<ApiResult<inferZodSchemaOutput<T>>> {
742773
try {
743774
const response = await zodfetch(schema, url, requestInit, {
744775
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)