From 03d576717bdebfea008778aac84b4efc1937c50d Mon Sep 17 00:00:00 2001 From: Chris Arderne Date: Tue, 30 Jun 2026 16:08:57 +0100 Subject: [PATCH 01/21] chore: upgrade to zod v4 --- .changeset/zod-4-support.md | 9 ++++ apps/supervisor/package.json | 2 +- apps/webapp/app/components/Feedback.tsx | 26 +++-------- .../errors/ConfigureErrorAlerts.tsx | 2 +- .../app/components/metrics/QueryWidget.tsx | 2 +- .../components/runs/v3/ReplayRunDialog.tsx | 2 +- .../schedules/PurchaseSchedulesModal.tsx | 2 +- .../app/models/orgIntegration.server.ts | 2 +- .../app/models/vercelIntegration.server.ts | 2 +- .../app/models/vercelSdkRecovery.server.ts | 4 +- .../v3/MetricDashboardPresenter.server.ts | 2 +- .../app/presenters/v3/SpanPresenter.server.ts | 3 ++ .../route.tsx | 2 +- .../route.tsx | 9 ++-- .../route.tsx | 2 +- .../route.tsx | 2 +- .../route.tsx | 4 +- .../route.tsx | 43 ++++++++----------- .../route.tsx | 2 +- .../route.tsx | 2 +- .../route.tsx | 2 +- .../route.tsx | 2 +- .../route.tsx | 2 +- .../route.tsx | 2 +- .../route.tsx | 2 +- .../route.tsx | 2 +- .../route.tsx | 2 +- .../route.tsx | 2 +- .../route.tsx | 14 +++--- .../route.tsx | 2 +- .../webapp/app/routes/_app.orgs.new/route.tsx | 2 +- .../app/routes/account._index/route.tsx | 4 +- .../app/routes/account.tokens/route.tsx | 4 +- .../webapp/app/routes/admin.feature-flags.tsx | 2 +- .../app/routes/api.v1.plain.customer-cards.ts | 2 +- .../webapp/app/routes/api.v1.prompts.$slug.ts | 2 +- apps/webapp/app/routes/api.v1.query.ts | 2 +- apps/webapp/app/routes/api.v1.schedules.ts | 1 + .../app/routes/confirm-basic-details.tsx | 2 +- apps/webapp/app/routes/invite-resend.tsx | 2 +- apps/webapp/app/routes/invite-revoke.tsx | 2 +- apps/webapp/app/routes/invites.tsx | 2 +- ...deployments.$deploymentShortCode.cancel.ts | 2 +- ...eployments.$deploymentShortCode.promote.ts | 2 +- ...ployments.$deploymentShortCode.rollback.ts | 2 +- ...urces.batches.$batchId.check-completion.ts | 2 +- .../app/routes/resources.branches.archive.tsx | 8 ++-- apps/webapp/app/routes/resources.feedback.ts | 5 +-- ...cts.$projectParam.env.$envParam.github.tsx | 2 +- ...ectParam.env.$envParam.runs.bulkaction.tsx | 2 +- .../route.tsx | 4 +- ...cts.$projectParam.env.$envParam.vercel.tsx | 2 +- .../route.tsx | 2 +- ....orgs.$organizationSlug.schedules-addon.ts | 2 +- .../resources.sessions.$sessionParam.close.ts | 2 +- .../resources.taskruns.$runParam.cancel.ts | 2 +- .../resources.taskruns.$runParam.replay.ts | 2 +- apps/webapp/app/services/apiAuth.server.ts | 4 +- ...authorizationRateLimitMiddleware.server.ts | 2 +- .../app/services/queryService.server.ts | 2 +- .../routeBuilders/apiBuilder.server.ts | 2 +- .../routeBuilders/dashboardBuilder.server.ts | 2 +- .../routeBuilders/dashboardBuilder.ts | 2 +- apps/webapp/app/utils/json.ts | 4 +- apps/webapp/app/utils/timeGranularity.ts | 16 ++++--- apps/webapp/app/v3/featureFlags.server.ts | 4 +- apps/webapp/app/v3/featureFlags.ts | 33 ++++++++------ apps/webapp/app/v3/schedules.ts | 37 ++++++++-------- .../v3/services/aiRunFilterService.server.ts | 2 +- .../app/v3/services/replayTaskRun.server.ts | 4 +- apps/webapp/app/v3/utils/zodPubSub.server.ts | 2 +- .../vercel/vercelProjectIntegrationSchema.ts | 2 +- apps/webapp/package.json | 4 +- internal-packages/clickhouse/package.json | 2 +- .../clickhouse/src/client/tsql.ts | 4 +- .../clickhouse/src/tsqlFunctions.test.ts | 2 +- internal-packages/compute/package.json | 2 +- internal-packages/compute/src/types.ts | 18 ++++---- internal-packages/emails/package.json | 2 +- internal-packages/run-engine/package.json | 2 +- .../run-engine/src/batch-queue/types.ts | 6 +-- .../schedule-engine/package.json | 2 +- internal-packages/tsql/package.json | 2 +- packages/cli-v3/package.json | 9 ++-- packages/cli-v3/src/cli/common.ts | 4 +- packages/cli-v3/src/commands/analyze.ts | 4 +- packages/cli-v3/src/mcp/tools/agentChat.ts | 2 +- packages/cli-v3/src/utilities/configFiles.ts | 4 +- packages/core/package.json | 11 +++-- packages/core/src/schemas/eventFilter.ts | 2 +- packages/core/src/schemas/json.ts | 4 +- packages/core/src/v3/apiClient/index.ts | 2 +- .../v3/runEngineWorker/supervisor/schemas.ts | 4 +- packages/core/src/v3/schemas/api.ts | 42 +++++++++--------- packages/core/src/v3/schemas/build.ts | 17 ++++---- packages/core/src/v3/schemas/common.ts | 12 +++--- packages/core/src/v3/schemas/eventFilter.ts | 2 +- packages/core/src/v3/schemas/messages.ts | 4 +- packages/core/src/v3/schemas/openTelemetry.ts | 2 +- packages/core/src/v3/schemas/query.ts | 2 +- packages/core/src/v3/schemas/resources.ts | 2 +- packages/core/src/v3/schemas/runEngine.ts | 2 +- packages/core/src/v3/schemas/schemas.ts | 14 +++--- packages/core/src/v3/serverOnly/httpServer.ts | 5 ++- packages/core/src/v3/types/tools.ts | 2 +- packages/core/src/v3/zodIpc.ts | 5 ++- packages/core/src/v3/zodMessageHandler.ts | 9 ++-- packages/core/src/v3/zodSocket.ts | 5 ++- packages/redis-worker/package.json | 7 ++- .../redis-worker/src/mollifier/schemas.ts | 6 +-- packages/redis-worker/src/queue.ts | 5 ++- packages/redis-worker/src/worker.ts | 4 +- packages/trigger-sdk/package.json | 4 +- 113 files changed, 300 insertions(+), 285 deletions(-) create mode 100644 .changeset/zod-4-support.md diff --git a/.changeset/zod-4-support.md b/.changeset/zod-4-support.md new file mode 100644 index 00000000000..c0578513828 --- /dev/null +++ b/.changeset/zod-4-support.md @@ -0,0 +1,9 @@ +--- +"@trigger.dev/core": minor +"@trigger.dev/sdk": minor +"trigger.dev": minor +"@trigger.dev/redis-worker": minor +"@trigger.dev/schema-to-json": minor +--- + +Add zod v4 compatibility. The `zod` peer dependency is widened to `^3.25.0 || ^4.0.0`, so projects can use zod 3.25+ or zod 4. Internal code was updated for zod v4 API changes (`ZodError.errors` → `.issues`, single-arg `z.record` → keyed, unified `error` option, `z.ZodSchema`/`z.AnyZodObject` → `z.ZodType`/`z.ZodObject`, `z.any()` object fields made `.optional()` to preserve v3 inference). No runtime behavior change for existing zod 3 users. diff --git a/apps/supervisor/package.json b/apps/supervisor/package.json index 2725fe2b729..d4555061708 100644 --- a/apps/supervisor/package.json +++ b/apps/supervisor/package.json @@ -23,7 +23,7 @@ "prom-client": "^15.1.0", "socket.io": "4.7.4", "std-env": "^3.8.0", - "zod": "3.25.76" + "zod": "4.4.3" }, "devDependencies": { "@internal/testcontainers": "workspace:*", diff --git a/apps/webapp/app/components/Feedback.tsx b/apps/webapp/app/components/Feedback.tsx index b679b042da1..7ceaa05fb53 100644 --- a/apps/webapp/app/components/Feedback.tsx +++ b/apps/webapp/app/components/Feedback.tsx @@ -1,11 +1,5 @@ -import { - getFormProps, - getSelectProps, - getInputProps, - getTextareaProps, - useForm, -} from "@conform-to/react"; -import { parseWithZod } from "@conform-to/zod"; +import { getFormProps, getSelectProps, getInputProps, getTextareaProps, useForm } from "@conform-to/react"; +import { parseWithZod } from "@conform-to/zod/v4"; import { InformationCircleIcon, ArrowUpCircleIcon } from "@heroicons/react/20/solid"; import { EnvelopeIcon, ShieldCheckIcon } from "@heroicons/react/24/solid"; import { Form, useActionData, useLocation, useNavigation, useSearchParams } from "@remix-run/react"; @@ -66,11 +60,11 @@ export function Feedback({ if ( navigation.formAction === "/resources/feedback" && navigation.state === "loading" && - Object.keys(form.allErrors).length === 0 + (form.errors === undefined || form.errors.length === 0) ) { setOpen(false); } - }, [navigation.formAction, navigation.state, form.allErrors]); + }, [navigation, form]); // Handle URL param functionality useEffect(() => { @@ -120,17 +114,9 @@ export function Feedback({ type === "concurrency" || type === "hipaa" ) &&
} -
+
- + {type === "feature" && ( ; diff --git a/apps/webapp/app/models/vercelIntegration.server.ts b/apps/webapp/app/models/vercelIntegration.server.ts index 3a1aaf4b8ea..324a1651270 100644 --- a/apps/webapp/app/models/vercelIntegration.server.ts +++ b/apps/webapp/app/models/vercelIntegration.server.ts @@ -129,7 +129,7 @@ export const VercelSecretSchema = z.object({ teamId: z.string().nullable().optional(), userId: z.string().optional(), installationId: z.string().optional(), - raw: z.record(z.any()).optional(), + raw: z.record(z.string(), z.any()).optional(), }); export type VercelSecret = z.infer; diff --git a/apps/webapp/app/models/vercelSdkRecovery.server.ts b/apps/webapp/app/models/vercelSdkRecovery.server.ts index d3e1bfd6961..31253fe430e 100644 --- a/apps/webapp/app/models/vercelSdkRecovery.server.ts +++ b/apps/webapp/app/models/vercelSdkRecovery.server.ts @@ -145,11 +145,11 @@ export const VercelSchemas = { .union([ z .object({ - envs: z.array(z.record(z.unknown())), + envs: z.array(z.record(z.string(), z.unknown())), pagination: z.unknown().optional(), }) .passthrough(), - z.array(z.record(z.unknown())), + z.array(z.record(z.string(), z.unknown())), ]) .transform((val) => (Array.isArray(val) ? { envs: val } : val)), diff --git a/apps/webapp/app/presenters/v3/MetricDashboardPresenter.server.ts b/apps/webapp/app/presenters/v3/MetricDashboardPresenter.server.ts index 0b84e971b2f..5c43a7ff63c 100644 --- a/apps/webapp/app/presenters/v3/MetricDashboardPresenter.server.ts +++ b/apps/webapp/app/presenters/v3/MetricDashboardPresenter.server.ts @@ -48,7 +48,7 @@ export const DashboardLayout = z.discriminatedUnion("version", [ z.object({ version: z.literal("1"), layout: z.array(LayoutItem), - widgets: z.record(Widget), + widgets: z.record(z.string(), Widget), }), ]); diff --git a/apps/webapp/app/presenters/v3/SpanPresenter.server.ts b/apps/webapp/app/presenters/v3/SpanPresenter.server.ts index b680a1c6a8e..209e906e50f 100644 --- a/apps/webapp/app/presenters/v3/SpanPresenter.server.ts +++ b/apps/webapp/app/presenters/v3/SpanPresenter.server.ts @@ -1024,6 +1024,9 @@ export class SpanPresenter extends BasePresenter { filePath: lockedWorker?.lockedBy?.filePath ?? "", }, run: { + // zod v4 types the run-context `context` (z.any) field as required; it was + // never populated here (optional under v3), so undefined preserves behavior. + context: undefined, id: run.friendlyId, createdAt: run.createdAt, tags: run.runTags, diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.invite/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.invite/route.tsx index b6fe0b09c68..b51a6d5ae1a 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.invite/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.invite/route.tsx @@ -1,5 +1,5 @@ import { getFormProps, getInputProps, useForm } from "@conform-to/react"; -import { parseWithZod } from "@conform-to/zod"; +import { parseWithZod } from "@conform-to/zod/v4"; import { ArrowUpCircleIcon, EnvelopeIcon, diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.alerts.new/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.alerts.new/route.tsx index f121900e901..275f6556eb3 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.alerts.new/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.alerts.new/route.tsx @@ -1,11 +1,11 @@ import { getFormProps, getInputProps, getSelectProps, useForm } from "@conform-to/react"; -import { parseWithZod } from "@conform-to/zod"; +import { parseWithZod } from "@conform-to/zod/v4"; import { HashtagIcon, LockClosedIcon } from "@heroicons/react/20/solid"; import { Form, useActionData, useNavigate, useNavigation } from "@remix-run/react"; import { type LoaderFunctionArgs } from "@remix-run/router"; import { type ActionFunctionArgs, json } from "@remix-run/server-runtime"; import { SlackIcon } from "@trigger.dev/companyicons"; -import { useEffect, useRef, useState } from "react"; +import { useEffect, useState } from "react"; import { typedjson, useTypedLoaderData } from "remix-typedjson"; import { z } from "zod"; import { InlineCode } from "~/components/code/InlineCode"; @@ -233,7 +233,6 @@ export default function Page() { const project = useProject(); const environment = useEnvironment(); const [currentAlertChannel, setCurrentAlertChannel] = useState(option ?? "EMAIL"); - const formRef = useRef(null); const [selectedSlackChannelValue, setSelectedSlackChannelValue] = useState(); @@ -267,7 +266,7 @@ export default function Page() { if (navigation.state !== "idle") return; if (lastSubmission !== undefined) return; - formRef.current?.reset(); + form.reset(); }, [navigation.state, lastSubmission]); return ( @@ -281,7 +280,7 @@ export default function Page() { > New alert - +
- {formEnvironments.errors} + {formEnvironments.errors} diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables.new/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables.new/route.tsx index 02d6ef00c4d..5409b8ab217 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables.new/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables.new/route.tsx @@ -1,5 +1,5 @@ import { getFormProps, useForm, type FieldMetadata, type FormMetadata } from "@conform-to/react"; -import { parseWithZod } from "@conform-to/zod"; +import { parseWithZod } from "@conform-to/zod/v4"; import { LockClosedIcon, LockOpenIcon, @@ -74,22 +74,19 @@ const schema = z.object({ if (i === "true") return true; return false; }, z.boolean()), - environmentIds: z.preprocess( - (i) => { - if (typeof i === "string") return [i]; - - if (Array.isArray(i)) { - const ids = i.filter((v) => typeof v === "string" && v !== ""); - if (ids.length === 0) { - return; - } - return ids; + environmentIds: z.preprocess((i) => { + if (typeof i === "string") return [i]; + + if (Array.isArray(i)) { + const ids = i.filter((v) => typeof v === "string" && v !== ""); + if (ids.length === 0) { + return; } + return ids; + } - return; - }, - z.array(z.string(), { required_error: "At least one environment is required" }) - ), + return; + }, z.array(z.string(), { error: "At least one environment is required" })), variables: z.preprocess((i) => { if (!Array.isArray(i)) { return []; @@ -247,14 +244,12 @@ export default function Page() { const [selectedEnvironmentIds, setSelectedEnvironmentIds] = useState>(new Set()); const [selectedBranchId, setSelectedBranchId] = useState(undefined); - // TODO for no we only support branch-specific env vars for Preview environments - // Mostly to keep the UX for setting consistent env-vars across Dev/Staging/Prod easier - const previewBranches = environments.filter( - (env) => env.type === "PREVIEW" && env.parentEnvironmentId !== null - ); - const nonBranchEnvironments = environments.filter((env) => env.parentEnvironmentId === null); + const branchEnvironments = environments.filter((env) => env.branchName); + const nonBranchEnvironments = environments.filter((env) => !env.branchName); const selectedEnvironments = environments.filter((env) => selectedEnvironmentIds.has(env.id)); - const previewIsSelected = selectedEnvironments.some((env) => env.type === "PREVIEW"); + const previewIsSelected = selectedEnvironments.some( + (env) => env.branchName !== null || env.type === "PREVIEW" + ); const isLoading = navigation.state !== "idle" && navigation.formMethod === "post"; @@ -436,7 +431,7 @@ export default function Page() { value={selectedBranchId ?? "all"} setValue={handleBranchChange} placeholder="All branches" - items={[{ id: "all", branchName: "All branches" }, ...previewBranches]} + items={[{ id: "all", branchName: "All branches" }, ...branchEnvironments]} className="w-fit min-w-52" filter={{ keys: [ @@ -444,7 +439,7 @@ export default function Page() { ], }} text={(val) => - val ? previewBranches.find((b) => b.id === val)?.branchName : null + val ? branchEnvironments.find((b) => b.id === val)?.branchName : null } dropdownIcon > diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.tsx index bd152e58033..d53c28df3cb 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.tsx @@ -1,5 +1,5 @@ import { getFormProps, getInputProps, useForm } from "@conform-to/react"; -import { parseWithZod } from "@conform-to/zod"; +import { parseWithZod } from "@conform-to/zod/v4"; import { BookOpenIcon, InformationCircleIcon, diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx index 6ec1aa7fce1..1b23b80390b 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx @@ -1,4 +1,4 @@ -import { parseWithZod } from "@conform-to/zod"; +import { parseWithZod } from "@conform-to/zod/v4"; import { BellAlertIcon } from "@heroicons/react/20/solid"; import { type MetaFunction, useFetcher, useRevalidator } from "@remix-run/react"; import { type ActionFunctionArgs, json, type LoaderFunctionArgs } from "@remix-run/server-runtime"; diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsx index 769a4868266..7ed9502b375 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsx @@ -1,4 +1,4 @@ -import { parseWithZod } from "@conform-to/zod"; +import { parseWithZod } from "@conform-to/zod/v4"; import { Outlet, useNavigate } from "@remix-run/react"; import { type ActionFunctionArgs, type LoaderFunctionArgs, json } from "@remix-run/server-runtime"; import { useCallback } from "react"; diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.query/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.query/route.tsx index 4f5a2c1e52d..5c8a7b77fd7 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.query/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.query/route.tsx @@ -160,7 +160,7 @@ export const action = async ({ request, params }: ActionFunctionArgs) => { if (!parsed.success) { return typedjson( { - error: parsed.error.errors.map((e) => e.message).join(", "), + error: parsed.error.issues.map((e) => e.message).join(", "), rows: null, columns: null, stats: null, diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.schedules.$scheduleParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.schedules.$scheduleParam/route.tsx index a3a0ef05470..3d154e26289 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.schedules.$scheduleParam/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.schedules.$scheduleParam/route.tsx @@ -1,4 +1,4 @@ -import { parseWithZod } from "@conform-to/zod"; +import { parseWithZod } from "@conform-to/zod/v4"; import { useLocation } from "@remix-run/react"; import { type ActionFunctionArgs, type LoaderFunctionArgs, json } from "@remix-run/server-runtime"; import { typedjson, useTypedLoaderData } from "remix-typedjson"; diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings.general/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings.general/route.tsx index ccc61fc5ac9..c2f3ec2aa22 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings.general/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings.general/route.tsx @@ -1,5 +1,5 @@ import { getFormProps, getInputProps, useForm } from "@conform-to/react"; -import { conformZodMessage, parseWithZod } from "@conform-to/zod"; +import { conformZodMessage, parseWithZod } from "@conform-to/zod/v4"; import { ExclamationTriangleIcon, FolderIcon, TrashIcon } from "@heroicons/react/20/solid"; import { Form, useActionData, useNavigation } from "@remix-run/react"; import { json } from "@remix-run/server-runtime"; diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings.integrations/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings.integrations/route.tsx index 9bc54b19bf1..706aad4d860 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings.integrations/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings.integrations/route.tsx @@ -1,5 +1,5 @@ import { getFormProps, getInputProps, useForm } from "@conform-to/react"; -import { parseWithZod } from "@conform-to/zod"; +import { parseWithZod } from "@conform-to/zod/v4"; import { Form, useActionData, useNavigation, useSearchParams } from "@remix-run/react"; import { json } from "@remix-run/server-runtime"; import React, { useCallback, useEffect, useRef, useState } from "react"; diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.test.tasks.$taskParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.test.tasks.$taskParam/route.tsx index ffe6dbcd038..a7cdf1e189a 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.test.tasks.$taskParam/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.test.tasks.$taskParam/route.tsx @@ -1,5 +1,5 @@ import { getFormProps, getInputProps, getSelectProps, useForm } from "@conform-to/react"; -import { parseWithZod } from "@conform-to/zod"; +import { parseWithZod } from "@conform-to/zod/v4"; import { CheckCircleIcon, RectangleStackIcon, diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.settings._index/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.settings._index/route.tsx index 348658ba9ef..29c1e0c26f8 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.settings._index/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.settings._index/route.tsx @@ -1,6 +1,6 @@ import colorWheelIcon from "../../assets/images/color-wheel.png"; import { getFormProps, getInputProps, useForm } from "@conform-to/react"; -import { conformZodMessage, parseWithZod } from "@conform-to/zod"; +import { conformZodMessage, parseWithZod } from "@conform-to/zod/v4"; import { CheckIcon, ExclamationTriangleIcon, diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.private-connections.new/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.private-connections.new/route.tsx index 1dc9753ac13..888d30c681a 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.private-connections.new/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.private-connections.new/route.tsx @@ -1,5 +1,5 @@ import { getFormProps, getInputProps, getSelectProps, useForm } from "@conform-to/react"; -import { parseWithZod } from "@conform-to/zod"; +import { parseWithZod } from "@conform-to/zod/v4"; import { Form, useActionData, useParams, type MetaFunction } from "@remix-run/react"; import { json, type ActionFunction, type LoaderFunctionArgs } from "@remix-run/server-runtime"; import { tryCatch } from "@trigger.dev/core/utils"; diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx index cc2a95571d0..afb0bacd37e 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx @@ -1,5 +1,5 @@ import { getFormProps, getInputProps, useForm } from "@conform-to/react"; -import { parseWithZod } from "@conform-to/zod"; +import { parseWithZod } from "@conform-to/zod/v4"; import { EnvelopeIcon, NoSymbolIcon, UserPlusIcon } from "@heroicons/react/20/solid"; import { DialogClose } from "@radix-ui/react-dialog"; import { @@ -1020,11 +1020,11 @@ export function PurchaseSeatsModal({ // when the role can't manage billing. The action enforces it independently. const noBillingTooltip = "You don't have permission to manage billing"; const trigger = canManageBilling ? ( - (triggerButton ?? ( + triggerButton ?? ( - )) + ) ) : triggerButton ? ( cloneElement(triggerButton, { disabled: true, tooltip: noBillingTooltip }) ) : ( @@ -1042,11 +1042,7 @@ export function PurchaseSeatsModal({ {trigger} {title} - +
@@ -1062,7 +1058,7 @@ export function PurchaseSeatsModal({ Total extra seats [], result.columns); return json({ format: "csv", diff --git a/apps/webapp/app/routes/api.v1.schedules.ts b/apps/webapp/app/routes/api.v1.schedules.ts index b5fd2fd968c..c7eb6f87281 100644 --- a/apps/webapp/app/routes/api.v1.schedules.ts +++ b/apps/webapp/app/routes/api.v1.schedules.ts @@ -108,6 +108,7 @@ export async function loader({ request }: LoaderFunctionArgs) { environmentId: authenticationResult.environment.id, page: params.data.page ?? 1, pageSize: params.data.perPage, + tasks: undefined, }); return { diff --git a/apps/webapp/app/routes/confirm-basic-details.tsx b/apps/webapp/app/routes/confirm-basic-details.tsx index 4e370913240..913f88df80f 100644 --- a/apps/webapp/app/routes/confirm-basic-details.tsx +++ b/apps/webapp/app/routes/confirm-basic-details.tsx @@ -1,5 +1,5 @@ import { getFormProps, getInputProps, useForm } from "@conform-to/react"; -import { conformZodMessage, parseWithZod } from "@conform-to/zod"; +import { conformZodMessage, parseWithZod } from "@conform-to/zod/v4"; import { ArrowRightIcon, EnvelopeIcon, UserIcon } from "@heroicons/react/20/solid"; import { HandRaisedIcon } from "@heroicons/react/24/solid"; import { RadioGroup } from "@radix-ui/react-radio-group"; diff --git a/apps/webapp/app/routes/invite-resend.tsx b/apps/webapp/app/routes/invite-resend.tsx index 3cba88bd20a..d64cc2d2aaf 100644 --- a/apps/webapp/app/routes/invite-resend.tsx +++ b/apps/webapp/app/routes/invite-resend.tsx @@ -1,4 +1,4 @@ -import { parseWithZod } from "@conform-to/zod"; +import { parseWithZod } from "@conform-to/zod/v4"; import { json } from "@remix-run/server-runtime"; import { env } from "process"; import { z } from "zod"; diff --git a/apps/webapp/app/routes/invite-revoke.tsx b/apps/webapp/app/routes/invite-revoke.tsx index fc1e4d0b79e..d4ab69f9c83 100644 --- a/apps/webapp/app/routes/invite-revoke.tsx +++ b/apps/webapp/app/routes/invite-revoke.tsx @@ -1,4 +1,4 @@ -import { parseWithZod } from "@conform-to/zod"; +import { parseWithZod } from "@conform-to/zod/v4"; import { json } from "@remix-run/server-runtime"; import { z } from "zod"; import { $replica } from "~/db.server"; diff --git a/apps/webapp/app/routes/invites.tsx b/apps/webapp/app/routes/invites.tsx index 6cf667c8677..acc0a09f4a3 100644 --- a/apps/webapp/app/routes/invites.tsx +++ b/apps/webapp/app/routes/invites.tsx @@ -1,5 +1,5 @@ import { getFormProps, useForm } from "@conform-to/react"; -import { parseWithZod } from "@conform-to/zod"; +import { parseWithZod } from "@conform-to/zod/v4"; import { EnvelopeIcon } from "@heroicons/react/20/solid"; import { type ActionFunction, type LoaderFunctionArgs, json, redirect } from "@remix-run/node"; import { Form, useActionData } from "@remix-run/react"; diff --git a/apps/webapp/app/routes/resources.$projectId.deployments.$deploymentShortCode.cancel.ts b/apps/webapp/app/routes/resources.$projectId.deployments.$deploymentShortCode.cancel.ts index 239d0982f9e..6acf14777c0 100644 --- a/apps/webapp/app/routes/resources.$projectId.deployments.$deploymentShortCode.cancel.ts +++ b/apps/webapp/app/routes/resources.$projectId.deployments.$deploymentShortCode.cancel.ts @@ -1,4 +1,4 @@ -import { parseWithZod } from "@conform-to/zod"; +import { parseWithZod } from "@conform-to/zod/v4"; import { json } from "@remix-run/node"; import { errAsync, fromPromise, okAsync } from "neverthrow"; import { z } from "zod"; diff --git a/apps/webapp/app/routes/resources.$projectId.deployments.$deploymentShortCode.promote.ts b/apps/webapp/app/routes/resources.$projectId.deployments.$deploymentShortCode.promote.ts index 979264516d1..529f627e5fb 100644 --- a/apps/webapp/app/routes/resources.$projectId.deployments.$deploymentShortCode.promote.ts +++ b/apps/webapp/app/routes/resources.$projectId.deployments.$deploymentShortCode.promote.ts @@ -1,4 +1,4 @@ -import { parseWithZod } from "@conform-to/zod"; +import { parseWithZod } from "@conform-to/zod/v4"; import { json } from "@remix-run/node"; import { z } from "zod"; import { $replica, prisma } from "~/db.server"; diff --git a/apps/webapp/app/routes/resources.$projectId.deployments.$deploymentShortCode.rollback.ts b/apps/webapp/app/routes/resources.$projectId.deployments.$deploymentShortCode.rollback.ts index d109e97c9d5..3caed9013b3 100644 --- a/apps/webapp/app/routes/resources.$projectId.deployments.$deploymentShortCode.rollback.ts +++ b/apps/webapp/app/routes/resources.$projectId.deployments.$deploymentShortCode.rollback.ts @@ -1,4 +1,4 @@ -import { parseWithZod } from "@conform-to/zod"; +import { parseWithZod } from "@conform-to/zod/v4"; import { json } from "@remix-run/node"; import { z } from "zod"; import { $replica, prisma } from "~/db.server"; diff --git a/apps/webapp/app/routes/resources.batches.$batchId.check-completion.ts b/apps/webapp/app/routes/resources.batches.$batchId.check-completion.ts index 2d1c6bc702b..3c7cd64523b 100644 --- a/apps/webapp/app/routes/resources.batches.$batchId.check-completion.ts +++ b/apps/webapp/app/routes/resources.batches.$batchId.check-completion.ts @@ -1,4 +1,4 @@ -import { parseWithZod } from "@conform-to/zod"; +import { parseWithZod } from "@conform-to/zod/v4"; import type { ActionFunction } from "@remix-run/node"; import { json } from "@remix-run/node"; import { z } from "zod"; diff --git a/apps/webapp/app/routes/resources.branches.archive.tsx b/apps/webapp/app/routes/resources.branches.archive.tsx index bf9e05a0d64..490cb77fc03 100644 --- a/apps/webapp/app/routes/resources.branches.archive.tsx +++ b/apps/webapp/app/routes/resources.branches.archive.tsx @@ -1,5 +1,5 @@ import { getFormProps, getInputProps, useForm } from "@conform-to/react"; -import { parseWithZod } from "@conform-to/zod"; +import { parseWithZod } from "@conform-to/zod/v4"; import { DialogClose } from "@radix-ui/react-dialog"; import { Form, useActionData, useLocation } from "@remix-run/react"; import { type ActionFunctionArgs } from "@remix-run/server-runtime"; @@ -100,10 +100,10 @@ export function ArchiveButton({ {...getFormProps(form)} className="w-full" > - + This will permanently make this branch{" "} @@ -116,7 +116,7 @@ export function ArchiveButton({ Once archived you can create a new branch with the same name. - {form.errors} + {form.errors?.join(", ")} diff --git a/apps/webapp/app/routes/resources.feedback.ts b/apps/webapp/app/routes/resources.feedback.ts index 4ace262f0cb..41f0f24249d 100644 --- a/apps/webapp/app/routes/resources.feedback.ts +++ b/apps/webapp/app/routes/resources.feedback.ts @@ -1,4 +1,4 @@ -import { parseWithZod } from "@conform-to/zod"; +import { parseWithZod } from "@conform-to/zod/v4"; import { type ActionFunctionArgs, json } from "@remix-run/server-runtime"; import { uiComponent } from "@team-plain/ui-components"; import { z } from "zod"; @@ -56,8 +56,7 @@ const feedbackTypeLiterals = Object.keys(feedbackTypes).map((key) => z.literal(k const feedbackType = z.union( [feedbackTypeLiterals[0], feedbackTypeLiterals[1], ...feedbackTypeLiterals.slice(2)], { - required_error: "Must be either 'bug' or 'feature'", - invalid_type_error: "Must be either 'bug' or 'feature'", + error: "Must be a valid feedback type", } ); diff --git a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.github.tsx b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.github.tsx index 4840a424d8c..e5dfd81a97e 100644 --- a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.github.tsx +++ b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.github.tsx @@ -1,5 +1,5 @@ import { getFormProps, getInputProps, useForm } from "@conform-to/react"; -import { parseWithZod } from "@conform-to/zod"; +import { parseWithZod } from "@conform-to/zod/v4"; import { ArrowUpCircleIcon, ArrowUpRightIcon, diff --git a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.bulkaction.tsx b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.bulkaction.tsx index 5bef068b994..66c5d92297a 100644 --- a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.bulkaction.tsx +++ b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.bulkaction.tsx @@ -1,4 +1,4 @@ -import { parseWithZod } from "@conform-to/zod"; +import { parseWithZod } from "@conform-to/zod/v4"; import { ArrowPathIcon, InformationCircleIcon } from "@heroicons/react/20/solid"; import { XCircleIcon } from "@heroicons/react/24/outline"; import { Form } from "@remix-run/react"; diff --git a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.schedules.new/route.tsx b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.schedules.new/route.tsx index 61ce814dbd5..32de08aca6d 100644 --- a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.schedules.new/route.tsx +++ b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.schedules.new/route.tsx @@ -1,5 +1,5 @@ import { getFormProps, getInputProps, getSelectProps, useForm } from "@conform-to/react"; -import { parseWithZod } from "@conform-to/zod"; +import { parseWithZod } from "@conform-to/zod/v4"; import { CheckIcon, XMarkIcon } from "@heroicons/react/20/solid"; import { type FetcherWithComponents, @@ -196,7 +196,7 @@ export function UpsertScheduleForm({ if (!result.success) { cronPatternResult = { isValid: false, - error: result.error.errors[0].message, + error: result.error.issues[0].message, }; } else { try { diff --git a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx index 792275a917d..019a28db063 100644 --- a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx +++ b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx @@ -1,5 +1,5 @@ import { getFormProps, useForm } from "@conform-to/react"; -import { parseWithZod } from "@conform-to/zod"; +import { parseWithZod } from "@conform-to/zod/v4"; import { CheckCircleIcon, ExclamationTriangleIcon } from "@heroicons/react/20/solid"; import { DialogClose } from "@radix-ui/react-dialog"; import { Form, useActionData, useFetcher, useLocation, useNavigation } from "@remix-run/react"; diff --git a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.waitpoints.$waitpointFriendlyId.complete/route.tsx b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.waitpoints.$waitpointFriendlyId.complete/route.tsx index e1ed1b2cc70..cf3940fdfe5 100644 --- a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.waitpoints.$waitpointFriendlyId.complete/route.tsx +++ b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.waitpoints.$waitpointFriendlyId.complete/route.tsx @@ -1,4 +1,4 @@ -import { parseWithZod } from "@conform-to/zod"; +import { parseWithZod } from "@conform-to/zod/v4"; import { Form, useLocation, useNavigation, useSubmit } from "@remix-run/react"; import { type ActionFunctionArgs, json } from "@remix-run/server-runtime"; import type { WaitpointTokenStatus } from "@trigger.dev/core/v3"; diff --git a/apps/webapp/app/routes/resources.orgs.$organizationSlug.schedules-addon.ts b/apps/webapp/app/routes/resources.orgs.$organizationSlug.schedules-addon.ts index d61bea07045..88f27b4d47c 100644 --- a/apps/webapp/app/routes/resources.orgs.$organizationSlug.schedules-addon.ts +++ b/apps/webapp/app/routes/resources.orgs.$organizationSlug.schedules-addon.ts @@ -1,4 +1,4 @@ -import { parseWithZod } from "@conform-to/zod"; +import { parseWithZod } from "@conform-to/zod/v4"; import { type ActionFunctionArgs, json } from "@remix-run/server-runtime"; import { tryCatch } from "@trigger.dev/core/v3"; import { z } from "zod"; diff --git a/apps/webapp/app/routes/resources.sessions.$sessionParam.close.ts b/apps/webapp/app/routes/resources.sessions.$sessionParam.close.ts index 26f573399c9..b0f820fccc7 100644 --- a/apps/webapp/app/routes/resources.sessions.$sessionParam.close.ts +++ b/apps/webapp/app/routes/resources.sessions.$sessionParam.close.ts @@ -1,4 +1,4 @@ -import { parseWithZod } from "@conform-to/zod"; +import { parseWithZod } from "@conform-to/zod/v4"; import { type ActionFunction, json } from "@remix-run/node"; import { z } from "zod"; import { $replica, prisma } from "~/db.server"; diff --git a/apps/webapp/app/routes/resources.taskruns.$runParam.cancel.ts b/apps/webapp/app/routes/resources.taskruns.$runParam.cancel.ts index cf6b0317780..7610f2c099c 100644 --- a/apps/webapp/app/routes/resources.taskruns.$runParam.cancel.ts +++ b/apps/webapp/app/routes/resources.taskruns.$runParam.cancel.ts @@ -1,4 +1,4 @@ -import { parseWithZod } from "@conform-to/zod"; +import { parseWithZod } from "@conform-to/zod/v4"; import { json } from "@remix-run/node"; import { z } from "zod"; import { prisma } from "~/db.server"; diff --git a/apps/webapp/app/routes/resources.taskruns.$runParam.replay.ts b/apps/webapp/app/routes/resources.taskruns.$runParam.replay.ts index e91ac2c5451..73e45b94d5d 100644 --- a/apps/webapp/app/routes/resources.taskruns.$runParam.replay.ts +++ b/apps/webapp/app/routes/resources.taskruns.$runParam.replay.ts @@ -1,4 +1,4 @@ -import { parseWithZod } from "@conform-to/zod"; +import { parseWithZod } from "@conform-to/zod/v4"; import { json, type LoaderFunctionArgs } from "@remix-run/node"; import { type EnvironmentType, prettyPrintPacket } from "@trigger.dev/core/v3"; import { type Prisma } from "@trigger.dev/database"; diff --git a/apps/webapp/app/services/apiAuth.server.ts b/apps/webapp/app/services/apiAuth.server.ts index 90fc9d2d3b7..8f2d4abad8e 100644 --- a/apps/webapp/app/services/apiAuth.server.ts +++ b/apps/webapp/app/services/apiAuth.server.ts @@ -768,7 +768,7 @@ export async function validateJWTTokenAndRenew( return; } - const renewedJwt = await renewJWTToken(payload.data); + const renewedJwt = await renewJWTToken(payload.data as Record); return { payload: payload.data, @@ -807,7 +807,7 @@ export async function validateJWTTokenAndRenew( } const renewedJwt = await generateJWTTokenForEnvironment(authenticatedEnv.environment, { - ...payload.data, + ...(payload.data as Record), }); // The environment carries secret material; log only non-secret fields. diff --git a/apps/webapp/app/services/authorizationRateLimitMiddleware.server.ts b/apps/webapp/app/services/authorizationRateLimitMiddleware.server.ts index 5fcd6eb5450..349c8cf0d7e 100644 --- a/apps/webapp/app/services/authorizationRateLimitMiddleware.server.ts +++ b/apps/webapp/app/services/authorizationRateLimitMiddleware.server.ts @@ -118,7 +118,7 @@ async function resolveLimitConfig( if (!parsedOverride.success) { logger.error("Error parsing rate limiter override", { override, - errors: parsedOverride.error.errors, + errors: parsedOverride.error.issues, }); return defaultLimiter; diff --git a/apps/webapp/app/services/queryService.server.ts b/apps/webapp/app/services/queryService.server.ts index 755da8ec3a3..33eb60e3cc8 100644 --- a/apps/webapp/app/services/queryService.server.ts +++ b/apps/webapp/app/services/queryService.server.ts @@ -381,7 +381,7 @@ export async function executeQuery( const result = await executeTSQL(queryClickhouse.reader, { ...baseOptions, - schema: z.record(z.any()), + schema: z.record(z.string(), z.any()), tableSchema: effectiveSchemas, transformValues: true, enforcedWhereClause, diff --git a/apps/webapp/app/services/routeBuilders/apiBuilder.server.ts b/apps/webapp/app/services/routeBuilders/apiBuilder.server.ts index 83212a6e8b2..fe01c3504e6 100644 --- a/apps/webapp/app/services/routeBuilders/apiBuilder.server.ts +++ b/apps/webapp/app/services/routeBuilders/apiBuilder.server.ts @@ -127,7 +127,7 @@ async function rejectRestrictedKeyWithoutAuthorization({ ); } -type AnyZodSchema = z.ZodFirstPartySchemaTypes | z.ZodDiscriminatedUnion; +type AnyZodSchema = z.ZodType; // A multi-resource auth check has two possible directions, and route authors // have to pick one explicitly: diff --git a/apps/webapp/app/services/routeBuilders/dashboardBuilder.server.ts b/apps/webapp/app/services/routeBuilders/dashboardBuilder.server.ts index 01bdb6d9b53..42d8acd4bf2 100644 --- a/apps/webapp/app/services/routeBuilders/dashboardBuilder.server.ts +++ b/apps/webapp/app/services/routeBuilders/dashboardBuilder.server.ts @@ -12,7 +12,7 @@ import type { AuthorizationOption, DashboardLoaderOptions, SessionUser } from ". import { fromZodError } from "zod-validation-error"; import type { z } from "zod"; -type AnyZodSchema = z.ZodFirstPartySchemaTypes | z.ZodDiscriminatedUnion; +type AnyZodSchema = z.ZodType; function loginRedirectFor(request: Request, override?: string): Response { if (override) return redirect(override); diff --git a/apps/webapp/app/services/routeBuilders/dashboardBuilder.ts b/apps/webapp/app/services/routeBuilders/dashboardBuilder.ts index a4603895915..b2b8d72f10b 100644 --- a/apps/webapp/app/services/routeBuilders/dashboardBuilder.ts +++ b/apps/webapp/app/services/routeBuilders/dashboardBuilder.ts @@ -17,7 +17,7 @@ import type { ActionFunctionArgs, LoaderFunctionArgs } from "@remix-run/server-r import type { RbacAbility, RbacResource } from "@trigger.dev/rbac"; import type { z } from "zod"; -type AnyZodSchema = z.ZodFirstPartySchemaTypes | z.ZodDiscriminatedUnion; +type AnyZodSchema = z.ZodType; type InferZod = T extends z.ZodFirstPartySchemaTypes | z.ZodDiscriminatedUnion ? z.infer diff --git a/apps/webapp/app/utils/json.ts b/apps/webapp/app/utils/json.ts index b3b44055958..e32960a08af 100644 --- a/apps/webapp/app/utils/json.ts +++ b/apps/webapp/app/utils/json.ts @@ -15,7 +15,7 @@ export function safeJsonParse(json?: string): unknown { export function safeJsonZodParse( schema: z.Schema, json: string -): z.SafeParseReturnType | undefined { +): z.ZodSafeParseResult | undefined { const parsed = safeJsonParse(json); if (parsed === null) { @@ -51,7 +51,7 @@ export async function safeBodyFromResponse( export async function safeParseBodyFromResponse( response: Response, schema: z.Schema -): Promise | undefined> { +): Promise | undefined> { try { const unknownJson = await response.json(); diff --git a/apps/webapp/app/utils/timeGranularity.ts b/apps/webapp/app/utils/timeGranularity.ts index 0c8ecc41ac8..1cfc6f80361 100644 --- a/apps/webapp/app/utils/timeGranularity.ts +++ b/apps/webapp/app/utils/timeGranularity.ts @@ -1,13 +1,15 @@ import { z } from "zod"; import parseDuration from "parse-duration"; -const DurationString = z.string().refine( - (val) => { - const ms = parseDuration(val); - return ms !== null && ms > 0; - }, - (val) => ({ message: `Invalid or non-positive duration string: "${val}"` }) -); +const DurationString = z + .string() + .refine( + (val) => { + const ms = parseDuration(val); + return ms !== null && ms > 0; + }, + { error: (issue) => `Invalid or non-positive duration string: "${issue.input}"` } + ); const BracketSchema = z.object({ max: z.union([z.literal("Infinity"), DurationString]), diff --git a/apps/webapp/app/v3/featureFlags.server.ts b/apps/webapp/app/v3/featureFlags.server.ts index c79bd8c01d7..1a435a48e52 100644 --- a/apps/webapp/app/v3/featureFlags.server.ts +++ b/apps/webapp/app/v3/featureFlags.server.ts @@ -37,7 +37,7 @@ export function makeFlag(_prisma: PrismaClientOrTransaction = prisma) { const parsed = flagSchema.safeParse(opts.overrides[opts.key]); if (parsed.success) { - return parsed.data; + return parsed.data as z.infer<(typeof FeatureFlagCatalog)[T]>; } } @@ -45,7 +45,7 @@ export function makeFlag(_prisma: PrismaClientOrTransaction = prisma) { const parsed = flagSchema.safeParse(value.value); if (parsed.success) { - return parsed.data; + return parsed.data as z.infer<(typeof FeatureFlagCatalog)[T]>; } } diff --git a/apps/webapp/app/v3/featureFlags.ts b/apps/webapp/app/v3/featureFlags.ts index f617cab16f3..d6ba1fb191f 100644 --- a/apps/webapp/app/v3/featureFlags.ts +++ b/apps/webapp/app/v3/featureFlags.ts @@ -112,8 +112,10 @@ export type FeatureFlagCatalog = z.infer; export function validateFeatureFlagValue( key: T, value: unknown -): z.SafeParseReturnType> { - return FeatureFlagCatalog[key].safeParse(value); +): ReturnType<(typeof FeatureFlagCatalog)[T]["safeParse"]> { + return FeatureFlagCatalog[key].safeParse(value) as ReturnType< + (typeof FeatureFlagCatalog)[T]["safeParse"] + >; } // Utility function to validate all feature flags at once @@ -162,23 +164,28 @@ export type FlagControlType = | { type: "number"; min?: number; max?: number } | { type: "string" }; -export function getFlagControlType(schema: z.ZodTypeAny): FlagControlType { - const typeName = schema._def.typeName; +export function getFlagControlType(schema: z.ZodType): FlagControlType { + // zod v4: schema.def.type is a lowercase tag ("boolean"/"enum"/"number"/...). + const def = schema.def as { + type: string; + checks?: Array<{ _zod?: { def?: { check?: string; value?: number } } }>; + }; - if (typeName === "ZodBoolean") { + if (def.type === "boolean") { return { type: "boolean" }; } - if (typeName === "ZodEnum") { - return { type: "enum", options: schema._def.values as string[] }; + if (def.type === "enum") { + return { type: "enum", options: (schema as z.ZodEnum).options as string[] }; } - // z.coerce.number() reports as ZodNumber; pull min/max out of its checks - // so the UI can render a constrained number input instead of free text. - if (typeName === "ZodNumber") { - const checks = (schema._def.checks ?? []) as Array<{ kind: string; value?: number }>; - const min = checks.find((c) => c.kind === "min")?.value; - const max = checks.find((c) => c.kind === "max")?.value; + // z.coerce.number() reports as "number"; pull min/max out of its v4 checks + // (each check carries `_zod.def.{check,value}`) so the UI can render a + // constrained number input instead of free text. + if (def.type === "number") { + const checks = def.checks ?? []; + const min = checks.find((c) => c._zod?.def?.check === "greater_than")?._zod?.def?.value; + const max = checks.find((c) => c._zod?.def?.check === "less_than")?._zod?.def?.value; return { type: "number", min, max }; } diff --git a/apps/webapp/app/v3/schedules.ts b/apps/webapp/app/v3/schedules.ts index 1653e053544..e0d33c77654 100644 --- a/apps/webapp/app/v3/schedules.ts +++ b/apps/webapp/app/v3/schedules.ts @@ -20,28 +20,25 @@ export const CronPattern = z.string().refine( return false; } }, - (val) => { - const parts = val.split(" "); - if (parts.length > 5) { - return { - message: "CRON expressions with seconds are not allowed", - }; - } + { + error: (issue) => { + const val = String(issue.input); + const parts = val.split(" "); + if (parts.length > 5) { + return "CRON expressions with seconds are not allowed"; + } - if (val === "") { - return { - message: "CRON expression is required", - }; - } + if (val === "") { + return "CRON expression is required"; + } - try { - parseExpression(val); - return { - message: "Unknown problem", - }; - } catch (e) { - return { message: e instanceof Error ? e.message : JSON.stringify(e) }; - } + try { + parseExpression(val); + return "Unknown problem"; + } catch (e) { + return e instanceof Error ? e.message : JSON.stringify(e); + } + }, } ); diff --git a/apps/webapp/app/v3/services/aiRunFilterService.server.ts b/apps/webapp/app/v3/services/aiRunFilterService.server.ts index 96b74ab3314..c3bc2b10e8b 100644 --- a/apps/webapp/app/v3/services/aiRunFilterService.server.ts +++ b/apps/webapp/app/v3/services/aiRunFilterService.server.ts @@ -258,7 +258,7 @@ export class AIRunFilterService { const validationResult = AIFilters.safeParse(output.filters); if (!validationResult.success) { logger.error("AI filter validation failed", { - errors: validationResult.error.errors, + errors: validationResult.error.issues, filters: output.filters, }); diff --git a/apps/webapp/app/v3/services/replayTaskRun.server.ts b/apps/webapp/app/v3/services/replayTaskRun.server.ts index d626ec2ae8c..8e71026252c 100644 --- a/apps/webapp/app/v3/services/replayTaskRun.server.ts +++ b/apps/webapp/app/v3/services/replayTaskRun.server.ts @@ -20,7 +20,9 @@ type OverrideOptions = { metadata?: unknown; bulkActionId?: string; triggerSource?: string; -} & RunOptionsData; + // zod v4 types `.optional().transform()` fields as required-with-undefined in the + // output; Partial keeps them omittable for callers passing a subset of overrides. +} & Partial; export class ReplayTaskRunService extends BaseService { public async call(existingTaskRun: TaskRun, overrideOptions: OverrideOptions = {}) { diff --git a/apps/webapp/app/v3/utils/zodPubSub.server.ts b/apps/webapp/app/v3/utils/zodPubSub.server.ts index c473443d169..6b1132a8715 100644 --- a/apps/webapp/app/v3/utils/zodPubSub.server.ts +++ b/apps/webapp/app/v3/utils/zodPubSub.server.ts @@ -54,7 +54,7 @@ class RedisZodSubscriber< eventName: K, listener: (payload: z.infer) => Promise ): void { - this._listeners.set(eventName as string, listener); + this._listeners.set(eventName as string, listener as Parameters[1]); } public async stopListening(): Promise { diff --git a/apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts b/apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts index 1399b87fc2b..cb55583c518 100644 --- a/apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts +++ b/apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts @@ -71,7 +71,7 @@ export type TriggerEnvironmentType = z.infer; * Only explicitly `false` entries disable sync. */ export const SyncEnvVarsMappingSchema = z - .record(EnvSlugSchema, z.record(z.string(), z.boolean())) + .partialRecord(EnvSlugSchema, z.record(z.string(), z.boolean())) .default({}); export type SyncEnvVarsMapping = z.infer; diff --git a/apps/webapp/package.json b/apps/webapp/package.json index 90dc46ac088..21a28f7a54e 100644 --- a/apps/webapp/package.json +++ b/apps/webapp/package.json @@ -216,9 +216,9 @@ "util": "^0.12.5", "uuid": "^14.0.0", "ws": "^8.11.0", - "zod": "3.25.76", + "zod": "4.4.3", "zod-error": "1.5.0", - "zod-validation-error": "^1.5.0" + "zod-validation-error": "^5.0.0" }, "devDependencies": { "@internal/clickhouse": "workspace:*", diff --git a/internal-packages/clickhouse/package.json b/internal-packages/clickhouse/package.json index ec5ec25e667..c3f87cf62dc 100644 --- a/internal-packages/clickhouse/package.json +++ b/internal-packages/clickhouse/package.json @@ -10,7 +10,7 @@ "@internal/tracing": "workspace:*", "@internal/tsql": "workspace:*", "@trigger.dev/core": "workspace:*", - "zod": "3.25.76", + "zod": "4.4.3", "zod-error": "1.5.0" }, "devDependencies": { diff --git a/internal-packages/clickhouse/src/client/tsql.ts b/internal-packages/clickhouse/src/client/tsql.ts index f61009237e4..99e6e402bc3 100644 --- a/internal-packages/clickhouse/src/client/tsql.ts +++ b/internal-packages/clickhouse/src/client/tsql.ts @@ -221,7 +221,7 @@ export async function executeTSQL( const queryFn = reader.queryWithStats({ name: isExplain ? `${options.name}-explain` : options.name, query: queryToExecute, - params: z.record(z.any()), + params: z.record(z.string(), z.any()), // EXPLAIN returns rows with an 'explain' column schema: isExplain ? z.object({ explain: z.string() }) : options.schema, settings: options.clickhouseSettings, @@ -257,7 +257,7 @@ export async function executeTSQL( const additionalQueryFn = reader.queryWithStats({ name: `${options.name}-explain-${explainType.name.toLowerCase()}`, query: explainType.query, - params: z.record(z.any()), + params: z.record(z.string(), z.any()), schema: z.object({ explain: z.string() }), settings: options.clickhouseSettings, logFields: { tsql: options.query }, diff --git a/internal-packages/clickhouse/src/tsqlFunctions.test.ts b/internal-packages/clickhouse/src/tsqlFunctions.test.ts index 8c1f9ec4221..188fd58e875 100644 --- a/internal-packages/clickhouse/src/tsqlFunctions.test.ts +++ b/internal-packages/clickhouse/src/tsqlFunctions.test.ts @@ -101,7 +101,7 @@ async function assertQueryExecutes(client: ClickhouseClient, tsqlQuery: string): const [error] = await executeTSQL(client, { name: "func-test", query: tsqlQuery, - schema: z.record(z.any()), + schema: z.record(z.string(), z.any()), enforcedWhereClause, tableSchema: [taskRunsSchema], }); diff --git a/internal-packages/compute/package.json b/internal-packages/compute/package.json index 4e221879382..c5045878f54 100644 --- a/internal-packages/compute/package.json +++ b/internal-packages/compute/package.json @@ -14,7 +14,7 @@ } }, "dependencies": { - "zod": "3.25.76" + "zod": "4.4.3" }, "devDependencies": { "rimraf": "6.0.1" diff --git a/internal-packages/compute/src/types.ts b/internal-packages/compute/src/types.ts index 5d8d5e26af9..fd878334efa 100644 --- a/internal-packages/compute/src/types.ts +++ b/internal-packages/compute/src/types.ts @@ -15,7 +15,7 @@ export const TemplateCreateRequestSchema = z.object({ callback: z .object({ url: z.string(), - metadata: z.record(z.string()).optional(), + metadata: z.record(z.string(), z.string()).optional(), }) .optional(), }); @@ -38,14 +38,14 @@ export type TemplateCreateResponse = z.infer; @@ -58,7 +58,7 @@ export type InstanceCreateResponse = z.infer; @@ -67,13 +67,13 @@ export type InstanceSnapshotRequest = z.infer; @@ -82,14 +82,14 @@ export const SnapshotCallbackPayloadSchema = z.discriminatedUnion("status", [ status: z.literal("completed"), snapshot_id: z.string(), instance_id: z.string(), - metadata: z.record(z.string()).optional(), + metadata: z.record(z.string(), z.string()).optional(), duration_ms: z.number().optional(), }), z.object({ status: z.literal("failed"), instance_id: z.string(), error: z.string().optional(), - metadata: z.record(z.string()).optional(), + metadata: z.record(z.string(), z.string()).optional(), duration_ms: z.number().optional(), }), ]); diff --git a/internal-packages/emails/package.json b/internal-packages/emails/package.json index 33dfe4c1d71..9f61faab457 100644 --- a/internal-packages/emails/package.json +++ b/internal-packages/emails/package.json @@ -18,7 +18,7 @@ "react-dom": "^18.2.0", "resend": "^3.2.0", "tiny-invariant": "^1.2.0", - "zod": "3.25.76" + "zod": "4.4.3" }, "devDependencies": { "@types/nodemailer": "^8.0.0", diff --git a/internal-packages/run-engine/package.json b/internal-packages/run-engine/package.json index 516e6a18696..026463ce0aa 100644 --- a/internal-packages/run-engine/package.json +++ b/internal-packages/run-engine/package.json @@ -32,7 +32,7 @@ "nanoid": "3.3.8", "redlock": "5.0.0-beta.2", "seedrandom": "^3.0.5", - "zod": "3.25.76", + "zod": "4.4.3", "p-map": "^6.0.0" }, "devDependencies": { diff --git a/internal-packages/run-engine/src/batch-queue/types.ts b/internal-packages/run-engine/src/batch-queue/types.ts index 9aa085e286a..0a8494abd00 100644 --- a/internal-packages/run-engine/src/batch-queue/types.ts +++ b/internal-packages/run-engine/src/batch-queue/types.ts @@ -37,7 +37,7 @@ export const BatchItem = z.object({ */ payloadType: z.string().optional(), /** Options for this specific item - stored as JSON */ - options: z.record(z.unknown()).optional(), + options: z.record(z.string(), z.unknown()).optional(), }); export type BatchItem = z.infer; @@ -68,7 +68,7 @@ export const BatchMeta = z.object({ /** Trigger version */ triggerVersion: z.string().optional(), /** Trace context */ - traceContext: z.record(z.unknown()).optional(), + traceContext: z.record(z.string(), z.unknown()).optional(), /** Whether span parent should be a link */ spanParentAsLink: z.boolean().optional(), /** Realtime streams version */ @@ -103,7 +103,7 @@ export const BatchItemFailure = z.object({ */ payload: z.string().optional(), /** The options that were used */ - options: z.record(z.unknown()).optional(), + options: z.record(z.string(), z.unknown()).optional(), /** Error message */ error: z.string(), /** Error code if available */ diff --git a/internal-packages/schedule-engine/package.json b/internal-packages/schedule-engine/package.json index 52a27a11e27..b8a53a75f6c 100644 --- a/internal-packages/schedule-engine/package.json +++ b/internal-packages/schedule-engine/package.json @@ -22,7 +22,7 @@ "cron-parser": "^4.9.0", "cronstrue": "^2.50.0", "nanoid": "3.3.8", - "zod": "3.25.76" + "zod": "4.4.3" }, "devDependencies": { "@internal/testcontainers": "workspace:*", diff --git a/internal-packages/tsql/package.json b/internal-packages/tsql/package.json index 0cac36e7b27..3c86630738f 100644 --- a/internal-packages/tsql/package.json +++ b/internal-packages/tsql/package.json @@ -8,7 +8,7 @@ "dependencies": { "@trigger.dev/core": "workspace:*", "antlr4ts": "0.5.0-alpha.4", - "zod": "3.25.76" + "zod": "4.4.3" }, "scripts": { "typecheck": "tsc --noEmit", diff --git a/packages/cli-v3/package.json b/packages/cli-v3/package.json index 6d0d90da98f..a486a90013e 100644 --- a/packages/cli-v3/package.json +++ b/packages/cli-v3/package.json @@ -68,7 +68,8 @@ "rimraf": "^6.0.1", "ts-essentials": "10.0.1", "tshy": "^3.0.2", - "tsx": "4.17.0" + "tsx": "4.17.0", + "zod": "4.4.3" }, "scripts": { "clean": "rimraf dist .tshy .tshy-build .turbo", @@ -146,8 +147,7 @@ "tinyglobby": "^0.2.10", "ws": "^8.18.0", "xdg-app-paths": "^8.3.0", - "zod": "3.25.76", - "zod-validation-error": "^1.5.0" + "zod-validation-error": "^5.0.0" }, "engines": { "node": ">=18.20.0" @@ -160,5 +160,8 @@ "default": "./dist/esm/index.js" } } + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" } } diff --git a/packages/cli-v3/src/cli/common.ts b/packages/cli-v3/src/cli/common.ts index 18d61a05803..dbb706e06c6 100644 --- a/packages/cli-v3/src/cli/common.ts +++ b/packages/cli-v3/src/cli/common.ts @@ -42,7 +42,7 @@ export async function handleTelemetry(action: () => Promise) { } } -export async function wrapCommandAction( +export async function wrapCommandAction, TResult>( name: string, schema: T, options: unknown, @@ -55,7 +55,7 @@ export async function wrapCommandAction( throw new Error(fromZodError(parsedOptions.error).toString()); } - logger.loggerLevel = parsedOptions.data.logLevel; + logger.loggerLevel = parsedOptions.data.logLevel as typeof logger.loggerLevel; logger.debug(`Running "${name}" with the following options`, { options: options, diff --git a/packages/cli-v3/src/commands/analyze.ts b/packages/cli-v3/src/commands/analyze.ts index dc03d36e2e5..e52112a53bb 100644 --- a/packages/cli-v3/src/commands/analyze.ts +++ b/packages/cli-v3/src/commands/analyze.ts @@ -113,14 +113,14 @@ const ImportSchema = z.object({ kind: ImportKind, external: z.boolean().optional(), original: z.string().optional(), - with: z.record(z.string()).optional(), + with: z.record(z.string(), z.string()).optional(), }); const InputSchema = z.object({ bytes: z.number(), imports: z.array(ImportSchema), format: z.enum(["cjs", "esm"]).optional(), - with: z.record(z.string()).optional(), + with: z.record(z.string(), z.string()).optional(), }); const OutputImportSchema = z.object({ diff --git a/packages/cli-v3/src/mcp/tools/agentChat.ts b/packages/cli-v3/src/mcp/tools/agentChat.ts index 992e3d436a1..3f5b390c046 100644 --- a/packages/cli-v3/src/mcp/tools/agentChat.ts +++ b/packages/cli-v3/src/mcp/tools/agentChat.ts @@ -70,7 +70,7 @@ const StartAgentChatInput = CommonProjectsInput.extend({ .describe("A unique conversation ID. Reuse to resume a conversation.") .optional(), clientData: z - .record(z.unknown()) + .record(z.string(), z.unknown()) .describe("Client data to include with every message (e.g. userId, model).") .optional(), preload: z diff --git a/packages/cli-v3/src/utilities/configFiles.ts b/packages/cli-v3/src/utilities/configFiles.ts index 4fee41d9cc4..541a58a68ad 100644 --- a/packages/cli-v3/src/utilities/configFiles.ts +++ b/packages/cli-v3/src/utilities/configFiles.ts @@ -22,13 +22,13 @@ const CliConfigProfileSettings = z.object({ }); type CliConfigProfileSettings = z.infer; -const OldCliConfigFile = z.record(CliConfigProfileSettings); +const OldCliConfigFile = z.record(z.string(), CliConfigProfileSettings); type OldCliConfigFile = z.infer; const CliConfigFile = z.object({ version: z.literal(2), currentProfile: z.string().default(DEFFAULT_PROFILE), - profiles: z.record(CliConfigProfileSettings), + profiles: z.record(z.string(), CliConfigProfileSettings), settings: z .object({ hasSeenMCPInstallPrompt: z.boolean().optional(), diff --git a/packages/core/package.json b/packages/core/package.json index 292d4ff421d..1003f7514b8 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -225,9 +225,8 @@ "std-env": "^3.8.1", "tinyexec": "^0.3.2", "uncrypto": "^0.1.3", - "zod": "3.25.76", "zod-error": "1.5.0", - "zod-validation-error": "^1.5.0" + "zod-validation-error": "^5.0.0" }, "devDependencies": { "@ai-sdk/provider-utils": "^1.0.22", @@ -245,7 +244,8 @@ "superjson": "^2.2.1", "ts-essentials": "10.0.1", "tshy": "^3.0.2", - "tsx": "4.17.0" + "tsx": "4.17.0", + "zod": "4.4.3" }, "engines": { "node": ">=18.20.0" @@ -663,5 +663,8 @@ "type": "module", "main": "./dist/commonjs/index.js", "types": "./dist/commonjs/index.d.ts", - "module": "./dist/esm/index.js" + "module": "./dist/esm/index.js", + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } } diff --git a/packages/core/src/schemas/eventFilter.ts b/packages/core/src/schemas/eventFilter.ts index 66b4d645466..25cf03b45e7 100644 --- a/packages/core/src/schemas/eventFilter.ts +++ b/packages/core/src/schemas/eventFilter.ts @@ -65,7 +65,7 @@ type EventMatcher = z.infer; export type EventFilter = { [key: string]: EventMatcher | EventFilter }; export const EventFilterSchema: z.ZodType = z.lazy(() => - z.record(z.union([EventMatcherSchema, EventFilterSchema])) + z.record(z.string(), z.union([EventMatcherSchema, EventFilterSchema])) ); export const EventRuleSchema = z.object({ diff --git a/packages/core/src/schemas/json.ts b/packages/core/src/schemas/json.ts index 79384db156a..32dd506c8ff 100644 --- a/packages/core/src/schemas/json.ts +++ b/packages/core/src/schemas/json.ts @@ -6,7 +6,7 @@ type Literal = z.infer; export type DeserializedJson = Literal | { [key: string]: DeserializedJson } | DeserializedJson[]; export const DeserializedJsonSchema: z.ZodType = z.lazy(() => - z.union([LiteralSchema, z.array(DeserializedJsonSchema), z.record(DeserializedJsonSchema)]) + z.union([LiteralSchema, z.array(DeserializedJsonSchema), z.record(z.string(), DeserializedJsonSchema)]) ); const SerializableSchema = z.union([ @@ -26,5 +26,5 @@ export type SerializableJson = | SerializableJson[]; export const SerializableJsonSchema: z.ZodType = z.lazy(() => - z.union([SerializableSchema, z.array(SerializableJsonSchema), z.record(SerializableJsonSchema)]) + z.union([SerializableSchema, z.array(SerializableJsonSchema), z.record(z.string(), SerializableJsonSchema)]) ); diff --git a/packages/core/src/v3/apiClient/index.ts b/packages/core/src/v3/apiClient/index.ts index 23f13f37573..759f6943971 100644 --- a/packages/core/src/v3/apiClient/index.ts +++ b/packages/core/src/v3/apiClient/index.ts @@ -1903,7 +1903,7 @@ export class ApiClient { async generateJWTClaims(requestOptions?: ZodFetchOptions): Promise> { return zodfetch( - z.record(z.any()), + z.record(z.string(), z.any()), `${this.baseUrl}/api/v1/auth/jwt/claims`, { method: "POST", diff --git a/packages/core/src/v3/runEngineWorker/supervisor/schemas.ts b/packages/core/src/v3/runEngineWorker/supervisor/schemas.ts index e5360c6ed6b..778bdb395b7 100644 --- a/packages/core/src/v3/runEngineWorker/supervisor/schemas.ts +++ b/packages/core/src/v3/runEngineWorker/supervisor/schemas.ts @@ -51,7 +51,7 @@ export type WorkerApiContinueRunExecutionRequestBody = z.infer< >; export const WorkerApiConnectRequestBody = z.object({ - metadata: z.record(z.any()), + metadata: z.record(z.string(), z.any()), }); export type WorkerApiConnectRequestBody = z.infer; @@ -104,7 +104,7 @@ export type WorkerApiRunAttemptStartRequestBody = z.infer< export const WorkerApiRunAttemptStartResponseBody = StartRunAttemptResult.and( z.object({ - envVars: z.record(z.string()), + envVars: z.record(z.string(), z.string()), }) ); export type WorkerApiRunAttemptStartResponseBody = z.infer< diff --git a/packages/core/src/v3/schemas/api.ts b/packages/core/src/v3/schemas/api.ts index 3f6ba0aeced..245351bf0c5 100644 --- a/packages/core/src/v3/schemas/api.ts +++ b/packages/core/src/v3/schemas/api.ts @@ -203,7 +203,7 @@ const ConcurrencyKeySchema = z.union([z.string(), z.number()]).transform((value) export const TriggerTaskRequestBody = z .object({ payload: z.any(), - context: z.any(), + context: z.any().optional(), options: z .object({ /** @deprecated engine v1 only */ @@ -254,7 +254,7 @@ export const TriggerTaskRequestBody = z machine: MachinePresetName.optional(), maxAttempts: z.number().int().optional(), maxDuration: z.number().optional(), - metadata: z.any(), + metadata: z.any().optional(), metadataType: z.string().optional(), payloadType: z.string().optional(), /** @@ -313,7 +313,7 @@ export type BatchTriggerTaskRequestBody = z.infer; * * `options` reuses the strict shape from BatchTriggerTaskItem so that the * Phase-2 streaming path validates option fields identically to the V2/V3 - * batch trigger endpoints — historically this used z.record(z.unknown()) and + * batch trigger endpoints — historically this used z.record(z.string(), z.unknown()) and * let invalid values (e.g. numeric concurrencyKey) reach Prisma. */ export const BatchItemNDJSON = z.object({ @@ -544,7 +544,7 @@ export const RescheduleRunRequestBody = z.object({ export type RescheduleRunRequestBody = z.infer; export const GetEnvironmentVariablesResponseBody = z.object({ - variables: z.record(z.string()), + variables: z.record(z.string(), z.string()), }); export type GetEnvironmentVariablesResponseBody = z.infer< @@ -658,7 +658,7 @@ export type CreateArtifactRequestBody = z.infer; export const ImportEnvironmentVariablesRequestBody = z.object({ - variables: z.record(z.string()), - parentVariables: z.record(z.string()).optional(), + variables: z.record(z.string(), z.string()), + parentVariables: z.record(z.string(), z.string()).optional(), override: z.boolean().optional(), // When omitted, variables default to non-secret (the DB default is false). isSecret: z.boolean().optional(), @@ -1443,7 +1443,7 @@ export const UpdateMetadataRequestBody = FlushedRunMetadata; export type UpdateMetadataRequestBody = z.infer; export const UpdateMetadataResponseBody = z.object({ - metadata: z.record(DeserializedJsonSchema), + metadata: z.record(z.string(), DeserializedJsonSchema), }); export type UpdateMetadataResponseBody = z.infer; @@ -1715,7 +1715,7 @@ export type CompleteWaitpointTokenRequestBody = z.infer; export const ResolvePromptRequestBody = z.object({ - variables: z.record(z.unknown()).default({}), + variables: z.record(z.string(), z.unknown()).default({}), label: z.string().optional(), version: z.number().optional(), }); @@ -2225,7 +2225,7 @@ export const ResolvePromptResponseBody = z.object({ template: z.string().optional(), text: z.string().optional(), model: z.string().optional().nullable(), - config: z.record(z.unknown()).optional().nullable(), + config: z.record(z.string(), z.unknown()).optional().nullable(), }), }); export type ResolvePromptResponseBody = z.infer; diff --git a/packages/core/src/v3/schemas/build.ts b/packages/core/src/v3/schemas/build.ts index 52cae0b2385..366db9ab48f 100644 --- a/packages/core/src/v3/schemas/build.ts +++ b/packages/core/src/v3/schemas/build.ts @@ -41,6 +41,7 @@ export const BuildManifest = z.object({ config: ConfigManifest, files: z.array(TaskFile), sources: z.record( + z.string(), z.object({ contents: z.string(), contentHash: z.string(), @@ -56,18 +57,18 @@ export const BuildManifest = z.object({ configPath: z.string(), externals: BuildExternal.array().optional(), build: z.object({ - env: z.record(z.string()).optional(), + env: z.record(z.string(), z.string()).optional(), commands: z.array(z.string()).optional(), }), customConditions: z.array(z.string()).optional(), deploy: z.object({ - env: z.record(z.string()).optional(), + env: z.record(z.string(), z.string()).optional(), sync: z .object({ - env: z.record(z.string()).optional(), - parentEnv: z.record(z.string()).optional(), - secretEnv: z.record(z.string()).optional(), - secretParentEnv: z.record(z.string()).optional(), + env: z.record(z.string(), z.string()).optional(), + parentEnv: z.record(z.string(), z.string()).optional(), + secretEnv: z.record(z.string(), z.string()).optional(), + secretParentEnv: z.record(z.string(), z.string()).optional(), }) .optional(), }), @@ -84,7 +85,7 @@ export const BuildManifest = z.object({ }) .optional(), /** Maps output file paths to their content hashes for deduplication during dev */ - outputHashes: z.record(z.string()).optional(), + outputHashes: z.record(z.string(), z.string()).optional(), /** Skills discovered and bundled into `.trigger/skills/{id}/` under `outputPath`. */ skills: SkillManifest.array().optional(), }); @@ -113,7 +114,7 @@ export const WorkerManifest = z.object({ runtime: BuildRuntime, runtimeVersion: z.string().optional(), customConditions: z.array(z.string()).optional(), - timings: z.record(z.number()).optional(), + timings: z.record(z.string(), z.number()).optional(), processKeepAlive: z .object({ enabled: z.boolean(), diff --git a/packages/core/src/v3/schemas/common.ts b/packages/core/src/v3/schemas/common.ts index a6ec4b1002d..d5ec88883dc 100644 --- a/packages/core/src/v3/schemas/common.ts +++ b/packages/core/src/v3/schemas/common.ts @@ -25,7 +25,7 @@ const MetadataOperationKey = z.string().refine(isSafeMetadataKey, { export const RunMetadataUpdateOperation = z.object({ type: z.literal("update"), - value: z.record(z.unknown()), + value: z.record(z.string(), z.unknown()), }); export type RunMetadataUpdateOperation = z.infer; @@ -81,7 +81,7 @@ export const RunMetadataChangeOperation = z.discriminatedUnion("type", [ export type RunMetadataChangeOperation = z.infer; export const FlushedRunMetadata = z.object({ - metadata: z.record(DeserializedJsonSchema).optional(), + metadata: z.record(z.string(), DeserializedJsonSchema).optional(), operations: z.array(RunMetadataChangeOperation).optional(), parentOperations: z.array(RunMetadataChangeOperation).optional(), rootOperations: z.array(RunMetadataChangeOperation).optional(), @@ -244,7 +244,7 @@ export const TaskRun = z.object({ idempotencyKeyScope: z.enum(["run", "attempt", "global"]).optional(), maxAttempts: z.number().optional(), version: z.string().optional(), - metadata: z.record(DeserializedJsonSchema).optional(), + metadata: z.record(z.string(), DeserializedJsonSchema).optional(), maxDuration: z.number().optional(), /** The priority of the run. Wih a value of 10 it will be dequeued before runs that were triggered 9 seconds before it (assuming they had no priority set). */ priority: z.number().optional(), @@ -365,7 +365,7 @@ export const TaskRunExecution = z.object({ attempt: TaskRunExecutionAttempt.passthrough(), run: TaskRun.and( z.object({ - traceContext: z.record(z.unknown()).optional(), + traceContext: z.record(z.string(), z.unknown()).optional(), realtimeStreamsVersion: z.string().optional(), }) ), @@ -408,7 +408,7 @@ export const V3TaskRun = z.object({ idempotencyKeyScope: z.enum(["run", "attempt", "global"]).optional(), maxAttempts: z.number().optional(), version: z.string().optional(), - metadata: z.record(DeserializedJsonSchema).optional(), + metadata: z.record(z.string(), DeserializedJsonSchema).optional(), maxDuration: z.number().optional(), context: z.unknown(), durationMs: z.number(), @@ -423,7 +423,7 @@ export const V3TaskRunExecution = z.object({ attempt: V3TaskRunExecutionAttempt, run: V3TaskRun.and( z.object({ - traceContext: z.record(z.unknown()).optional(), + traceContext: z.record(z.string(), z.unknown()).optional(), }) ), queue: TaskRunExecutionQueue, diff --git a/packages/core/src/v3/schemas/eventFilter.ts b/packages/core/src/v3/schemas/eventFilter.ts index 6ddda362108..698ae49184f 100644 --- a/packages/core/src/v3/schemas/eventFilter.ts +++ b/packages/core/src/v3/schemas/eventFilter.ts @@ -65,5 +65,5 @@ type EventMatcher = z.infer; export type EventFilter = { [key: string]: EventMatcher | EventFilter }; export const EventFilter: z.ZodType = z.lazy(() => - z.record(z.union([EventMatcher, EventFilter])) + z.record(z.string(), z.union([EventMatcher, EventFilter])) ); diff --git a/packages/core/src/v3/schemas/messages.ts b/packages/core/src/v3/schemas/messages.ts index 1389fcddc40..65784ce4a6a 100644 --- a/packages/core/src/v3/schemas/messages.ts +++ b/packages/core/src/v3/schemas/messages.ts @@ -90,10 +90,10 @@ export const WorkerToExecutorMessageCatalog = { message: z.object({ version: z.literal("v1").default("v1"), execution: TaskRunExecution, - traceContext: z.record(z.unknown()), + traceContext: z.record(z.string(), z.unknown()), metadata: ServerBackgroundWorker, metrics: TaskRunExecutionMetrics.optional(), - env: z.record(z.string()).optional(), + env: z.record(z.string(), z.string()).optional(), isWarmStart: z.boolean().optional(), }), }, diff --git a/packages/core/src/v3/schemas/openTelemetry.ts b/packages/core/src/v3/schemas/openTelemetry.ts index 47d9d9a50d9..785805b94e5 100644 --- a/packages/core/src/v3/schemas/openTelemetry.ts +++ b/packages/core/src/v3/schemas/openTelemetry.ts @@ -43,7 +43,7 @@ export type AttemptFailedSpanEvent = z.infer; export const OtherSpanEvent = z.object({ name: z.string(), time: z.coerce.date(), - properties: z.record(z.unknown()), + properties: z.record(z.string(), z.unknown()), }); export type OtherSpanEvent = z.infer; diff --git a/packages/core/src/v3/schemas/query.ts b/packages/core/src/v3/schemas/query.ts index 027259e8b74..f5a6d8be902 100644 --- a/packages/core/src/v3/schemas/query.ts +++ b/packages/core/src/v3/schemas/query.ts @@ -21,7 +21,7 @@ export type QueryExecuteRequestBody = z.infer; */ export const QueryExecuteJSONResponseBody = z.object({ format: z.literal("json"), - results: z.array(z.record(z.any())), + results: z.array(z.record(z.string(), z.any())), }); export type QueryExecuteJSONResponseBody = z.infer; diff --git a/packages/core/src/v3/schemas/resources.ts b/packages/core/src/v3/schemas/resources.ts index 753324d1257..923c29355be 100644 --- a/packages/core/src/v3/schemas/resources.ts +++ b/packages/core/src/v3/schemas/resources.ts @@ -46,7 +46,7 @@ export const PromptResource = z.object({ /** Default model identifier */ model: z.string().optional(), /** Default model config (temperature, maxTokens, etc.) */ - config: z.record(z.unknown()).optional(), + config: z.record(z.string(), z.unknown()).optional(), /** JSONSchema7 for template variables */ variableSchema: z.unknown().optional(), }); diff --git a/packages/core/src/v3/schemas/runEngine.ts b/packages/core/src/v3/schemas/runEngine.ts index 92144364494..5d9be4b1aa8 100644 --- a/packages/core/src/v3/schemas/runEngine.ts +++ b/packages/core/src/v3/schemas/runEngine.ts @@ -288,7 +288,7 @@ export const DequeuedMessage = z.object({ machine: MachinePreset, attemptNumber: z.number(), masterQueue: z.string(), - traceContext: z.record(z.unknown()), + traceContext: z.record(z.string(), z.unknown()), annotations: RunAnnotations.optional(), }), environment: z.object({ diff --git a/packages/core/src/v3/schemas/schemas.ts b/packages/core/src/v3/schemas/schemas.ts index 0b12e7ae3db..9fd4470cc2d 100644 --- a/packages/core/src/v3/schemas/schemas.ts +++ b/packages/core/src/v3/schemas/schemas.ts @@ -31,8 +31,8 @@ export type TaskRunExecutionMetrics = z.infer; export const TaskRunExecutionPayload = z.object({ execution: TaskRunExecution, - traceContext: z.record(z.unknown()), - environment: z.record(z.string()).optional(), + traceContext: z.record(z.string(), z.unknown()), + environment: z.record(z.string(), z.string()).optional(), metrics: TaskRunExecutionMetrics.optional(), }); @@ -56,8 +56,8 @@ export type V3ProdTaskRunExecution = z.infer; export const V3ProdTaskRunExecutionPayload = z.object({ execution: V3ProdTaskRunExecution, - traceContext: z.record(z.unknown()), - environment: z.record(z.string()).optional(), + traceContext: z.record(z.string(), z.unknown()), + environment: z.record(z.string(), z.string()).optional(), metrics: TaskRunExecutionMetrics.optional(), }); @@ -231,7 +231,7 @@ const promptMetadata = { description: z.string().optional(), content: z.string().optional(), model: z.string().optional(), - config: z.record(z.unknown()).optional(), + config: z.record(z.string(), z.unknown()).optional(), variableSchema: z.unknown().optional(), }; @@ -320,8 +320,8 @@ export const TaskRunExecutionLazyAttemptPayload = z.object({ messageId: z.string(), isTest: z.boolean(), isReplay: z.boolean().default(false), - traceContext: z.record(z.unknown()), - environment: z.record(z.string()).optional(), + traceContext: z.record(z.string(), z.unknown()), + environment: z.record(z.string(), z.string()).optional(), metrics: TaskRunExecutionMetrics.optional(), }); diff --git a/packages/core/src/v3/serverOnly/httpServer.ts b/packages/core/src/v3/serverOnly/httpServer.ts index 8360067d583..1ada1c37ac8 100644 --- a/packages/core/src/v3/serverOnly/httpServer.ts +++ b/packages/core/src/v3/serverOnly/httpServer.ts @@ -302,13 +302,14 @@ export class HttpServer { return { success: true, data }; } - const parsed = schema.safeParse(data); + // zod v4 narrows the generic schema to $ZodTypes here; cast to the public ZodType (has safeParse in v3 & v4) + const parsed = (schema as unknown as z.ZodType).safeParse(data); if (!parsed.success) { return { success: false, error: parsed.error.message }; } - return { success: true, data: parsed.data }; + return { success: true, data: parsed.data as TSchema extends z.ZodFirstPartySchemaTypes ? z.infer : TData }; } private parseQueryParams(url: string): Record { diff --git a/packages/core/src/v3/types/tools.ts b/packages/core/src/v3/types/tools.ts index 9df741dc0ad..abf5646f9da 100644 --- a/packages/core/src/v3/types/tools.ts +++ b/packages/core/src/v3/types/tools.ts @@ -14,7 +14,7 @@ export type inferToolParameters = export function convertToolParametersToSchema( toolParameters: TToolParameters ): Schema { - return toolParameters instanceof z.ZodSchema + return toolParameters instanceof z.ZodType ? toolParameters : convertAISchemaToTaskSchema(toolParameters); } diff --git a/packages/core/src/v3/zodIpc.ts b/packages/core/src/v3/zodIpc.ts index d810d255d7f..7ee56ea654b 100644 --- a/packages/core/src/v3/zodIpc.ts +++ b/packages/core/src/v3/zodIpc.ts @@ -84,7 +84,8 @@ class ZodIpcMessageHandler< return; } - const ack = await handler(parsedMessage.payload, this.#sender); + // parsedMessage.payload is parsed output at runtime; zod v4 distinguishes input/output + const ack = await handler(parsedMessage.payload as Parameters[0], this.#sender); return ack; } @@ -320,7 +321,7 @@ export class ZodIpcConnection< ); }, timeoutInMs ?? defaultTimeoutInMs); - this.#acks.set(currentId, { resolve, reject, timeout }); + this.#acks.set(currentId, { resolve: resolve as (value: unknown) => void, reject, timeout }); const schema = this.opts.emitSchema[type]?.["message"]; diff --git a/packages/core/src/v3/zodMessageHandler.ts b/packages/core/src/v3/zodMessageHandler.ts index 2836822faf5..645e290edf3 100644 --- a/packages/core/src/v3/zodMessageHandler.ts +++ b/packages/core/src/v3/zodMessageHandler.ts @@ -109,7 +109,8 @@ export class ZodMessageHandler }; } - const ack = await handler(parsedMessage.data.payload); + // parsedMessage.data.payload is parsed output at runtime; zod v4 distinguishes input/output + const ack = await handler(parsedMessage.data.payload as Parameters[0]); return { success: true, @@ -277,7 +278,8 @@ export class ZodMessageSender { } try { - await this.#sender({ type, payload, version: "v1" }); + // send the validated/normalized output so zod transforms, defaults, and coercions are applied + await this.#sender({ type, payload: parsedPayload.data, version: "v1" }); } catch (error) { console.error("[ZodMessageSender] Failed to send message", error); } @@ -332,7 +334,8 @@ export async function sendMessageInCatalog = { diff --git a/packages/core/src/v3/zodSocket.ts b/packages/core/src/v3/zodSocket.ts index 32488dd3551..86fa0c1f197 100644 --- a/packages/core/src/v3/zodSocket.ts +++ b/packages/core/src/v3/zodSocket.ts @@ -128,7 +128,8 @@ export class ZodSocketMessageHandler[0]); return ack; } @@ -314,7 +315,7 @@ export class ZodSocketMessageSender>; } } diff --git a/packages/redis-worker/package.json b/packages/redis-worker/package.json index 7c014c23436..90e1ac36bdb 100644 --- a/packages/redis-worker/package.json +++ b/packages/redis-worker/package.json @@ -28,7 +28,6 @@ "nanoid": "^5.0.7", "p-limit": "^6.2.0", "seedrandom": "^3.0.5", - "zod": "3.25.76", "cron-parser": "^4.9.0" }, "devDependencies": { @@ -39,7 +38,8 @@ "@types/seedrandom": "^3.0.8", "rimraf": "6.0.1", "tsup": "^8.4.0", - "tsx": "4.17.0" + "tsx": "4.17.0", + "zod": "4.4.3" }, "engines": { "node": ">=18.20.0" @@ -53,5 +53,8 @@ "import": "./dist/index.js", "require": "./dist/index.cjs" } + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" } } diff --git a/packages/redis-worker/src/mollifier/schemas.ts b/packages/redis-worker/src/mollifier/schemas.ts index db75ed6e452..f395bad5888 100644 --- a/packages/redis-worker/src/mollifier/schemas.ts +++ b/packages/redis-worker/src/mollifier/schemas.ts @@ -56,12 +56,12 @@ export const BufferEntrySchema = z.object({ // so an entry written by an accept Lua predating this field — or one // surviving across the deploy that introduced it — still parses instead // of being silently dropped on pop. - createdAtMicros: stringToInt.default("0"), + createdAtMicros: stringToInt.default(0), // Drainer-ack flag: `true` once the drainer has materialised this run // into PG. The hash persists for a short grace TTL after ack so direct // reads (retrieve, trace, etc.) still resolve while PG replica lag // settles. Absent on pre-ack entries. - materialised: stringToBool.default("false"), + materialised: stringToBool.default(false), // Denormalised pointer to the Redis idempotency lookup key (set when // the run was accepted with an idempotency key, empty otherwise). The // ack Lua reads this to DEL the lookup atomically with marking the @@ -71,7 +71,7 @@ export const BufferEntrySchema = z.object({ // Incremented atomically by the CAS metadata Lua. Matches the // semantic of `TaskRun.metadataVersion` on the PG side (which the // UpdateMetadataService uses for the same retry-on-conflict pattern). - metadataVersion: stringToInt.default("0"), + metadataVersion: stringToInt.default(0), lastError: stringToError.optional(), }); diff --git a/packages/redis-worker/src/queue.ts b/packages/redis-worker/src/queue.ts index becfb3813ea..63e53b334e4 100644 --- a/packages/redis-worker/src/queue.ts +++ b/packages/redis-worker/src/queue.ts @@ -217,7 +217,8 @@ export class SimpleQueue { continue; } - const validatedItem = schema.safeParse(parsedItem.item); + // zod v4 narrows the generic schema union here; cast to public ZodType (has safeParse in v3 & v4) + const validatedItem = (schema as z.ZodType).safeParse(parsedItem.item); if (!validatedItem.success) { this.logger.error("Invalid item in queue", { @@ -236,7 +237,7 @@ export class SimpleQueue { dequeuedItems.push({ id, job: parsedItem.job, - item: validatedItem.data, + item: validatedItem.data as QueueItem["item"], visibilityTimeoutMs, attempt: parsedItem.attempt ?? 0, timestamp, diff --git a/packages/redis-worker/src/worker.ts b/packages/redis-worker/src/worker.ts index b9240acaa63..d7e9e098eac 100644 --- a/packages/redis-worker/src/worker.ts +++ b/packages/redis-worker/src/worker.ts @@ -1088,11 +1088,13 @@ class Worker { const enqueued = await this.enqueueOnce({ id: identifier, job, + // job is a runtime string here, so K widens and the payload can't match a + // single catalog entry statically; the shape is correct for the cron job. payload: { timestamp: scheduledAt.getTime(), lastTimestamp: lastTimestamp?.getTime(), cron, - }, + } as z.infer, availableAt, }); diff --git a/packages/trigger-sdk/package.json b/packages/trigger-sdk/package.json index 64de5ea6d96..65237e95480 100644 --- a/packages/trigger-sdk/package.json +++ b/packages/trigger-sdk/package.json @@ -101,13 +101,13 @@ "tshy": "^3.0.2", "tsx": "4.17.0", "typed-emitter": "^2.1.0", - "zod": "3.25.76" + "zod": "4.4.3" }, "peerDependencies": { "@ai-sdk/otel": ">=1.0.0-0 <2", "ai": "^5.0.0 || ^6.0.0 || >=7.0.0-canary <8", "react": "^18.0 || ^19.0", - "zod": "^3.0.0 || ^4.0.0" + "zod": "^3.25.0 || ^4.0.0" }, "peerDependenciesMeta": { "@ai-sdk/otel": { From a13357ca6196c7c2cec0c4ba7b510fae99f42f01 Mon Sep 17 00:00:00 2001 From: Chris Arderne Date: Tue, 30 Jun 2026 16:17:39 +0100 Subject: [PATCH 02/21] format, typecheck, fix build --- apps/webapp/app/components/Feedback.tsx | 20 ++++++++++++--- .../billing/BillingAlertsSection.tsx | 2 +- .../billing/BillingLimitConfigSection.tsx | 4 +-- .../billing/BillingLimitRecoveryPanel.tsx | 6 ++--- .../route.tsx | 25 +++++++++++-------- .../route.tsx | 2 +- .../route.tsx | 10 +++++--- .../route.tsx | 4 +-- .../app/routes/confirm-basic-details.tsx | 2 +- .../app/routes/resources.branches.archive.tsx | 5 +++- .../app/routes/resources.branches.create.tsx | 2 +- apps/webapp/app/utils/timeGranularity.ts | 16 ++++++------ packages/core/src/schemas/json.ts | 12 +++++++-- packages/core/src/v3/serverOnly/httpServer.ts | 5 +++- 14 files changed, 74 insertions(+), 41 deletions(-) diff --git a/apps/webapp/app/components/Feedback.tsx b/apps/webapp/app/components/Feedback.tsx index 7ceaa05fb53..fc60bdad1b7 100644 --- a/apps/webapp/app/components/Feedback.tsx +++ b/apps/webapp/app/components/Feedback.tsx @@ -1,4 +1,10 @@ -import { getFormProps, getSelectProps, getInputProps, getTextareaProps, useForm } from "@conform-to/react"; +import { + getFormProps, + getSelectProps, + getInputProps, + getTextareaProps, + useForm, +} from "@conform-to/react"; import { parseWithZod } from "@conform-to/zod/v4"; import { InformationCircleIcon, ArrowUpCircleIcon } from "@heroicons/react/20/solid"; import { EnvelopeIcon, ShieldCheckIcon } from "@heroicons/react/24/solid"; @@ -114,9 +120,17 @@ export function Feedback({ type === "concurrency" || type === "hipaa" ) &&
} - +
- + {type === "feature" && ( v === "on" || v === true || v === "true", z.boolean()) diff --git a/apps/webapp/app/components/billing/BillingLimitRecoveryPanel.tsx b/apps/webapp/app/components/billing/BillingLimitRecoveryPanel.tsx index 1dfcdd73707..c0ecbc402c2 100644 --- a/apps/webapp/app/components/billing/BillingLimitRecoveryPanel.tsx +++ b/apps/webapp/app/components/billing/BillingLimitRecoveryPanel.tsx @@ -1,5 +1,5 @@ import { getFormProps, useForm, type SubmissionResult } from "@conform-to/react"; -import { parseWithZod } from "@conform-to/zod"; +import { parseWithZod } from "@conform-to/zod/v4"; import { Form, useActionData, useNavigation } from "@remix-run/react"; import { useEffect, useMemo, useRef, useState } from "react"; import { z } from "zod"; @@ -21,7 +21,7 @@ export const billingLimitRecoveryFormSchema = z .object({ action: z.enum(["increase", "remove"]), newAmount: z.coerce - .number({ invalid_type_error: "Not a valid amount" }) + .number({ error: "Not a valid amount" }) .positive("Amount must be greater than 0") .optional(), resumeMode: z.enum(["queue", "new_only"]), @@ -87,7 +87,7 @@ export function BillingLimitRecoveryPanel({ }, defaultValue: { action: "increase", - newAmount: suggestedNewLimitDollars, + newAmount: String(suggestedNewLimitDollars), resumeMode: "queue", }, }); diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables.new/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables.new/route.tsx index 5409b8ab217..d3402165808 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables.new/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables.new/route.tsx @@ -74,19 +74,22 @@ const schema = z.object({ if (i === "true") return true; return false; }, z.boolean()), - environmentIds: z.preprocess((i) => { - if (typeof i === "string") return [i]; - - if (Array.isArray(i)) { - const ids = i.filter((v) => typeof v === "string" && v !== ""); - if (ids.length === 0) { - return; + environmentIds: z.preprocess( + (i) => { + if (typeof i === "string") return [i]; + + if (Array.isArray(i)) { + const ids = i.filter((v) => typeof v === "string" && v !== ""); + if (ids.length === 0) { + return; + } + return ids; } - return ids; - } - return; - }, z.array(z.string(), { error: "At least one environment is required" })), + return; + }, + z.array(z.string(), { error: "At least one environment is required" }) + ), variables: z.preprocess((i) => { if (!Array.isArray(i)) { return []; diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.billing-limits/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.billing-limits/route.tsx index a7790c4ee48..c61bbe72b8f 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.billing-limits/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.billing-limits/route.tsx @@ -1,4 +1,4 @@ -import { parseWithZod } from "@conform-to/zod"; +import { parseWithZod } from "@conform-to/zod/v4"; import type { MetaFunction } from "@remix-run/react"; import { json, redirect } from "@remix-run/server-runtime"; import { tryCatch } from "@trigger.dev/core"; diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx index afb0bacd37e..2212a69828d 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx @@ -1020,11 +1020,11 @@ export function PurchaseSeatsModal({ // when the role can't manage billing. The action enforces it independently. const noBillingTooltip = "You don't have permission to manage billing"; const trigger = canManageBilling ? ( - triggerButton ?? ( + (triggerButton ?? ( - ) + )) ) : triggerButton ? ( cloneElement(triggerButton, { disabled: true, tooltip: noBillingTooltip }) ) : ( @@ -1042,7 +1042,11 @@ export function PurchaseSeatsModal({ {trigger} {title} - +
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx index ddc21398a33..b58ab15b768 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx @@ -388,12 +388,12 @@ export default function Page() { {canCreateV3Projects ? ( ) : ( )} diff --git a/apps/webapp/app/routes/confirm-basic-details.tsx b/apps/webapp/app/routes/confirm-basic-details.tsx index 913f88df80f..c15d5991705 100644 --- a/apps/webapp/app/routes/confirm-basic-details.tsx +++ b/apps/webapp/app/routes/confirm-basic-details.tsx @@ -316,7 +316,7 @@ export default function Page() { ) : ( <> - + )} diff --git a/apps/webapp/app/routes/resources.branches.archive.tsx b/apps/webapp/app/routes/resources.branches.archive.tsx index 490cb77fc03..dfc542f793f 100644 --- a/apps/webapp/app/routes/resources.branches.archive.tsx +++ b/apps/webapp/app/routes/resources.branches.archive.tsx @@ -100,7 +100,10 @@ export function ArchiveButton({ {...getFormProps(form)} className="w-full" > - + { - const ms = parseDuration(val); - return ms !== null && ms > 0; - }, - { error: (issue) => `Invalid or non-positive duration string: "${issue.input}"` } - ); +const DurationString = z.string().refine( + (val) => { + const ms = parseDuration(val); + return ms !== null && ms > 0; + }, + { error: (issue) => `Invalid or non-positive duration string: "${issue.input}"` } +); const BracketSchema = z.object({ max: z.union([z.literal("Infinity"), DurationString]), diff --git a/packages/core/src/schemas/json.ts b/packages/core/src/schemas/json.ts index 32dd506c8ff..985c3073a39 100644 --- a/packages/core/src/schemas/json.ts +++ b/packages/core/src/schemas/json.ts @@ -6,7 +6,11 @@ type Literal = z.infer; export type DeserializedJson = Literal | { [key: string]: DeserializedJson } | DeserializedJson[]; export const DeserializedJsonSchema: z.ZodType = z.lazy(() => - z.union([LiteralSchema, z.array(DeserializedJsonSchema), z.record(z.string(), DeserializedJsonSchema)]) + z.union([ + LiteralSchema, + z.array(DeserializedJsonSchema), + z.record(z.string(), DeserializedJsonSchema), + ]) ); const SerializableSchema = z.union([ @@ -26,5 +30,9 @@ export type SerializableJson = | SerializableJson[]; export const SerializableJsonSchema: z.ZodType = z.lazy(() => - z.union([SerializableSchema, z.array(SerializableJsonSchema), z.record(z.string(), SerializableJsonSchema)]) + z.union([ + SerializableSchema, + z.array(SerializableJsonSchema), + z.record(z.string(), SerializableJsonSchema), + ]) ); diff --git a/packages/core/src/v3/serverOnly/httpServer.ts b/packages/core/src/v3/serverOnly/httpServer.ts index 1ada1c37ac8..28bf0af9ac7 100644 --- a/packages/core/src/v3/serverOnly/httpServer.ts +++ b/packages/core/src/v3/serverOnly/httpServer.ts @@ -309,7 +309,10 @@ export class HttpServer { return { success: false, error: parsed.error.message }; } - return { success: true, data: parsed.data as TSchema extends z.ZodFirstPartySchemaTypes ? z.infer : TData }; + return { + success: true, + data: parsed.data as TSchema extends z.ZodFirstPartySchemaTypes ? z.infer : TData, + }; } private parseQueryParams(url: string): Record { From 61327b61b62c84e98bb964d54fda0f3462d6238e Mon Sep 17 00:00:00 2001 From: Chris Arderne Date: Tue, 30 Jun 2026 16:23:01 +0100 Subject: [PATCH 03/21] fix failing test --- apps/webapp/test/billingLimitsRoute.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/webapp/test/billingLimitsRoute.test.ts b/apps/webapp/test/billingLimitsRoute.test.ts index 66f1ac5a7b5..278ee90d791 100644 --- a/apps/webapp/test/billingLimitsRoute.test.ts +++ b/apps/webapp/test/billingLimitsRoute.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { parseWithZod } from "@conform-to/zod"; +import { parseWithZod } from "@conform-to/zod/v4"; import { billingAlertsSchema } from "~/components/billing/BillingAlertsSection"; import { billingLimitFormSchema, From 5ec053b17683bbac80749bd46db4af89d99a4095 Mon Sep 17 00:00:00 2001 From: Chris Arderne Date: Fri, 3 Jul 2026 16:07:44 +0100 Subject: [PATCH 04/21] format, lint --- .../route.tsx | 46 +++++++++---------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.test.tasks.$taskParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.test.tasks.$taskParam/route.tsx index a7cdf1e189a..87ddb127205 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.test.tasks.$taskParam/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.test.tasks.$taskParam/route.tsx @@ -433,37 +433,15 @@ function StandardTaskForm({ const [showTemplateCreatedSuccessMessage, setShowTemplateCreatedSuccessMessage] = useState(false); - const [ - form, - { - environmentId, - payload, - metadata, - taskIdentifier, - delaySeconds, - ttlSeconds, - idempotencyKey, - idempotencyKeyTTLSeconds, - queue, - concurrencyKey, - maxAttempts, - maxDurationSeconds, - triggerSource, - tags, - version, - machine, - region, - prioritySeconds, - }, - ] = useForm({ + const [form, fields] = useForm({ id: "test-task", // TODO: type this lastResult: lastSubmission as any, onSubmit(event, { formData }) { event.preventDefault(); - formData.set(payload.name, currentPayloadJson.current); - formData.set(metadata.name, currentMetadataJson.current); + formData.set(fields.payload.name, currentPayloadJson.current); + formData.set(fields.metadata.name, currentMetadataJson.current); submit(formData, { method: "POST" }); }, @@ -471,6 +449,24 @@ function StandardTaskForm({ return parseWithZod(formData, { schema: TestTaskData }); }, }); + const { + environmentId, + taskIdentifier, + delaySeconds, + ttlSeconds, + idempotencyKey, + idempotencyKeyTTLSeconds, + queue, + concurrencyKey, + maxAttempts, + maxDurationSeconds, + triggerSource, + tags, + version, + machine, + region, + prioritySeconds, + } = fields; return ( From f1603b28cb99cd0710afb55879d1658be43dee1e Mon Sep 17 00:00:00 2001 From: Chris Arderne Date: Fri, 3 Jul 2026 16:25:45 +0100 Subject: [PATCH 05/21] cli dep zod4, update changelog --- .changeset/zod-4-support.md | 4 +++- packages/cli-v3/package.json | 7 ++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.changeset/zod-4-support.md b/.changeset/zod-4-support.md index c0578513828..fd739c5be76 100644 --- a/.changeset/zod-4-support.md +++ b/.changeset/zod-4-support.md @@ -6,4 +6,6 @@ "@trigger.dev/schema-to-json": minor --- -Add zod v4 compatibility. The `zod` peer dependency is widened to `^3.25.0 || ^4.0.0`, so projects can use zod 3.25+ or zod 4. Internal code was updated for zod v4 API changes (`ZodError.errors` → `.issues`, single-arg `z.record` → keyed, unified `error` option, `z.ZodSchema`/`z.AnyZodObject` → `z.ZodType`/`z.ZodObject`, `z.any()` object fields made `.optional()` to preserve v3 inference). No runtime behavior change for existing zod 3 users. +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. + +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). diff --git a/packages/cli-v3/package.json b/packages/cli-v3/package.json index a486a90013e..1051d4666c8 100644 --- a/packages/cli-v3/package.json +++ b/packages/cli-v3/package.json @@ -68,8 +68,7 @@ "rimraf": "^6.0.1", "ts-essentials": "10.0.1", "tshy": "^3.0.2", - "tsx": "4.17.0", - "zod": "4.4.3" + "tsx": "4.17.0" }, "scripts": { "clean": "rimraf dist .tshy .tshy-build .turbo", @@ -147,6 +146,7 @@ "tinyglobby": "^0.2.10", "ws": "^8.18.0", "xdg-app-paths": "^8.3.0", + "zod": "4.4.3", "zod-validation-error": "^5.0.0" }, "engines": { @@ -160,8 +160,5 @@ "default": "./dist/esm/index.js" } } - }, - "peerDependencies": { - "zod": "^3.25.0 || ^4.0.0" } } From 526b89210640c1a1d04a7e46ed617d12b8e24b29 Mon Sep 17 00:00:00 2001 From: Chris Arderne Date: Fri, 3 Jul 2026 16:54:19 +0100 Subject: [PATCH 06/21] bump to platform@1.2.0 --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 2708b06be73..3f7acafe0a6 100644 --- a/package.json +++ b/package.json @@ -143,6 +143,7 @@ "brace-expansion@>=5 <5.0.6": "5.0.6", "@jsonhero/json-infer-types>ip-address": "^10.2.0", "@modelcontextprotocol/sdk@>=1.26.0>express-rate-limit": "^8.6.0" + "zod": "4.4.3" }, "onlyBuiltDependencies": [ "@depot/cli", From 80da777793b65b0fe42b88b5ae78406128c93223 Mon Sep 17 00:00:00 2001 From: Chris Arderne Date: Mon, 3 Aug 2026 15:35:42 +0100 Subject: [PATCH 07/21] updates --- apps/webapp/app/routes/login._index/route.tsx | 4 +- apps/webapp/app/routes/login.sso/route.tsx | 2 +- .../app/services/queryService.server.ts | 2 +- package.json | 2 +- pnpm-lock.yaml | 312 +++++++++--------- 5 files changed, 158 insertions(+), 164 deletions(-) diff --git a/apps/webapp/app/routes/login._index/route.tsx b/apps/webapp/app/routes/login._index/route.tsx index ce186c73461..81c5d26d30b 100644 --- a/apps/webapp/app/routes/login._index/route.tsx +++ b/apps/webapp/app/routes/login._index/route.tsx @@ -38,9 +38,7 @@ import { cn } from "~/utils/cn"; // format validation in the browser and renders the styled inline error. // Server-side errors still surface via the session-backed authError below. const emailSchema = z.object({ - email: z - .string({ required_error: "Enter your email address" }) - .email("Enter a valid email address"), + email: z.string({ error: "Enter your email address" }).email("Enter a valid email address"), }); function LastUsedBadge({ className }: { className?: string }) { diff --git a/apps/webapp/app/routes/login.sso/route.tsx b/apps/webapp/app/routes/login.sso/route.tsx index 1ed85497353..d03b7de2893 100644 --- a/apps/webapp/app/routes/login.sso/route.tsx +++ b/apps/webapp/app/routes/login.sso/route.tsx @@ -67,7 +67,7 @@ const ERROR_MESSAGES: Record = { // inline error before submit. Server-side errors keep flowing via ?error=. const ssoEmailSchema = z.object({ email: z - .string({ required_error: "Enter your enterprise email address" }) + .string({ error: "Enter your enterprise email address" }) .email("Enter a valid email address"), }); diff --git a/apps/webapp/app/services/queryService.server.ts b/apps/webapp/app/services/queryService.server.ts index 33eb60e3cc8..77f2b3f4178 100644 --- a/apps/webapp/app/services/queryService.server.ts +++ b/apps/webapp/app/services/queryService.server.ts @@ -381,7 +381,7 @@ export async function executeQuery( const result = await executeTSQL(queryClickhouse.reader, { ...baseOptions, - schema: z.record(z.string(), z.any()), + schema: z.record(z.string(), z.any()) as unknown as TOut, tableSchema: effectiveSchemas, transformValues: true, enforcedWhereClause, diff --git a/package.json b/package.json index 3f7acafe0a6..0718cb0178f 100644 --- a/package.json +++ b/package.json @@ -142,7 +142,7 @@ "brace-expansion@>=2 <2.0.3": "2.0.3", "brace-expansion@>=5 <5.0.6": "5.0.6", "@jsonhero/json-infer-types>ip-address": "^10.2.0", - "@modelcontextprotocol/sdk@>=1.26.0>express-rate-limit": "^8.6.0" + "@modelcontextprotocol/sdk@>=1.26.0>express-rate-limit": "^8.6.0", "zod": "4.4.3" }, "onlyBuiltDependencies": [ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 45163c161bb..4ed4f2765d1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -53,6 +53,7 @@ overrides: brace-expansion@>=5 <5.0.6: 5.0.6 '@jsonhero/json-infer-types>ip-address': ^10.2.0 '@modelcontextprotocol/sdk@>=1.26.0>express-rate-limit': ^8.6.0 + zod: 4.4.3 patchedDependencies: '@changesets/assemble-release-plan@5.2.4': @@ -191,8 +192,8 @@ importers: specifier: ^3.8.0 version: 3.8.1 zod: - specifier: 3.25.76 - version: 3.25.76 + specifier: 4.4.3 + version: 4.4.3 devDependencies: '@internal/testcontainers': specifier: workspace:* @@ -205,13 +206,13 @@ importers: dependencies: '@ai-sdk/anthropic': specifier: ^3.0.0 - version: 3.0.84(zod@3.25.76) + version: 3.0.84(zod@4.4.3) '@ai-sdk/openai': specifier: ^3.0.0 - version: 3.0.41(zod@3.25.76) + version: 3.0.41(zod@4.4.3) '@ai-sdk/react': specifier: ^3.0.0 - version: 3.0.170(react@18.3.1)(zod@3.25.76) + version: 3.0.170(react@18.3.1)(zod@4.4.3) '@ariakit/react': specifier: ^0.4.6 version: 0.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -268,7 +269,7 @@ importers: version: 1.19.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@conform-to/zod': specifier: ^1.2.2 - version: 1.19.4(zod@3.25.76) + version: 1.19.4(zod@4.4.3) '@depot/cli': specifier: 0.0.1-cli.2.80.0 version: 0.0.1-cli.2.80.0 @@ -490,7 +491,7 @@ importers: version: link:../../internal-packages/otlp-importer '@trigger.dev/platform': specifier: 1.3.0 - version: 1.3.0(zod@3.25.76) + version: 1.3.0(zod@4.4.3) '@trigger.dev/plugins': specifier: workspace:* version: link:../../packages/plugins @@ -526,7 +527,7 @@ importers: version: 1.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) ai: specifier: 6.0.116 - version: 6.0.116(zod@3.25.76) + version: 6.0.116(zod@4.4.3) assert: specifier: ^2.1.0 version: 2.1.0 @@ -727,7 +728,7 @@ importers: version: 0.3.1(@remix-run/react@2.17.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@6.0.3))(@remix-run/server-runtime@2.17.5(typescript@6.0.3))(react@18.3.1) remix-utils: specifier: ^7.7.0 - version: 7.7.0(@remix-run/node@2.17.5(typescript@6.0.3))(@remix-run/react@2.17.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@6.0.3))(@remix-run/router@1.23.3(patch_hash=33e2966e9ef36aa09955cec9922c5063227b83429157a71ba226369a08627ac9))(crypto-js@4.2.0)(intl-parse-accept-language@1.0.0)(react@18.3.1)(zod@3.25.76) + version: 7.7.0(@remix-run/node@2.17.5(typescript@6.0.3))(@remix-run/react@2.17.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@6.0.3))(@remix-run/router@1.23.3(patch_hash=33e2966e9ef36aa09955cec9922c5063227b83429157a71ba226369a08627ac9))(crypto-js@4.2.0)(intl-parse-accept-language@1.0.0)(react@18.3.1)(zod@4.4.3) semver: specifier: ^7.5.0 version: 7.8.1 @@ -777,14 +778,14 @@ importers: specifier: 8.21.0 version: 8.21.0(bufferutil@4.0.9) zod: - specifier: 3.25.76 - version: 3.25.76 + specifier: 4.4.3 + version: 4.4.3 zod-error: specifier: 1.5.0 version: 1.5.0 zod-validation-error: - specifier: ^1.5.0 - version: 1.5.0(zod@3.25.76) + specifier: ^5.0.0 + version: 5.0.0(zod@4.4.3) devDependencies: '@internal/clickhouse': specifier: workspace:* @@ -899,7 +900,7 @@ importers: version: 0.15.18 evalite: specifier: 1.0.0-beta.16 - version: 1.0.0-beta.16(ai@6.0.116(zod@3.25.76))(better-sqlite3@11.10.0)(bufferutil@4.0.9) + version: 1.0.0-beta.16(ai@6.0.116(zod@4.4.3))(better-sqlite3@11.10.0)(bufferutil@4.0.9) npm-run-all: specifier: ^4.1.5 version: 4.1.5 @@ -975,8 +976,8 @@ importers: specifier: workspace:* version: link:../../packages/core zod: - specifier: 3.25.76 - version: 3.25.76 + specifier: 4.4.3 + version: 4.4.3 zod-error: specifier: 1.5.0 version: 1.5.0 @@ -991,8 +992,8 @@ importers: internal-packages/compute: dependencies: zod: - specifier: 3.25.76 - version: 3.25.76 + specifier: 4.4.3 + version: 4.4.3 devDependencies: rimraf: specifier: 6.0.1 @@ -1002,7 +1003,7 @@ importers: dependencies: '@ai-sdk/anthropic': specifier: ^3.0.0 - version: 3.0.84(zod@3.25.76) + version: 3.0.84(zod@4.4.3) '@internal/dashboard-agent-db': specifier: workspace:* version: link:../dashboard-agent-db @@ -1011,10 +1012,10 @@ importers: version: link:../../packages/trigger-sdk ai: specifier: 6.0.116 - version: 6.0.116(zod@3.25.76) + version: 6.0.116(zod@4.4.3) zod: - specifier: 3.25.76 - version: 3.25.76 + specifier: 4.4.3 + version: 4.4.3 devDependencies: '@ai-sdk/provider': specifier: 3.0.8 @@ -1088,8 +1089,8 @@ importers: specifier: ^1.2.0 version: 1.3.1 zod: - specifier: 3.25.76 - version: 3.25.76 + specifier: 4.4.3 + version: 4.4.3 devDependencies: '@types/nodemailer': specifier: ^8.0.0 @@ -1256,8 +1257,8 @@ importers: specifier: ^3.0.5 version: 3.0.5 zod: - specifier: 3.25.76 - version: 3.25.76 + specifier: 4.4.3 + version: 4.4.3 devDependencies: '@internal/testcontainers': specifier: workspace:* @@ -1334,8 +1335,8 @@ importers: specifier: 3.3.8 version: 3.3.8 zod: - specifier: 3.25.76 - version: 3.25.76 + specifier: 4.4.3 + version: 4.4.3 devDependencies: '@internal/testcontainers': specifier: workspace:* @@ -1446,8 +1447,8 @@ importers: specifier: 0.5.0-alpha.4 version: 0.5.0-alpha.4(patch_hash=b5d41129ddbf7c4cbb0288244b2c8d041ee0803c5102c1181c180408d3b579f4) zod: - specifier: 3.25.76 - version: 3.25.76 + specifier: 4.4.3 + version: 4.4.3 devDependencies: antlr4ts-cli: specifier: 0.5.0-alpha.4 @@ -1506,7 +1507,7 @@ importers: version: 0.0.1-cli.2.80.0 '@modelcontextprotocol/sdk': specifier: ^1.25.2 - version: 1.25.2(hono@4.12.25)(supports-color@10.0.0)(zod@3.25.76) + version: 1.25.2(hono@4.12.25)(supports-color@10.0.0)(zod@4.4.3) '@opentelemetry/api': specifier: 1.9.1 version: 1.9.1 @@ -1685,11 +1686,11 @@ importers: specifier: ^8.3.0 version: 8.3.0 zod: - specifier: 3.25.76 - version: 3.25.76 + specifier: 4.4.3 + version: 4.4.3 zod-validation-error: - specifier: ^1.5.0 - version: 1.5.0(zod@3.25.76) + specifier: ^5.0.0 + version: 5.0.0(zod@4.4.3) devDependencies: '@epic-web/test-server': specifier: ^0.1.0 @@ -1844,19 +1845,16 @@ importers: uncrypto: specifier: ^0.1.3 version: 0.1.3 - zod: - specifier: 3.25.76 - version: 3.25.76 zod-error: specifier: 1.5.0 version: 1.5.0 zod-validation-error: - specifier: ^1.5.0 - version: 1.5.0(zod@3.25.76) + specifier: ^5.0.0 + version: 5.0.0(zod@4.4.3) devDependencies: '@ai-sdk/provider-utils': specifier: ^1.0.22 - version: 1.0.22(zod@3.25.76) + version: 1.0.22(zod@4.4.3) '@arethetypeswrong/cli': specifier: ^0.18.5 version: 0.18.5 @@ -1877,10 +1875,10 @@ importers: version: 4.0.14 ai: specifier: 6.0.116 - version: 6.0.116(zod@3.25.76) + version: 6.0.116(zod@4.4.3) ai-v7: specifier: npm:ai@7.0.0-canary.159 - version: ai@7.0.0-canary.159(zod@3.25.76) + version: ai@7.0.0-canary.159(zod@4.4.3) defu: specifier: ^6.1.5 version: 6.1.7 @@ -1902,6 +1900,9 @@ importers: tsx: specifier: 4.17.0 version: 4.17.0 + zod: + specifier: 4.4.3 + version: 4.4.3 packages/plugins: dependencies: @@ -2016,9 +2017,6 @@ importers: seedrandom: specifier: ^3.0.5 version: 3.0.5 - zod: - specifier: 3.25.76 - version: 3.25.76 devDependencies: '@internal/redis': specifier: workspace:* @@ -2044,6 +2042,9 @@ importers: tsx: specifier: 4.17.0 version: 4.17.0 + zod: + specifier: 4.4.3 + version: 4.4.3 packages/rsc: dependencies: @@ -2097,11 +2098,11 @@ importers: specifier: ^3 version: 3.17.1 zod: - specifier: 3.25.76 - version: 3.25.76 + specifier: 4.4.3 + version: 4.4.3 zod-to-json-schema: specifier: ^3.24.0 - version: 3.24.6(zod@3.25.76) + version: 3.24.6(zod@4.4.3) devDependencies: '@arethetypeswrong/cli': specifier: ^0.18.5 @@ -2135,7 +2136,7 @@ importers: dependencies: '@ai-sdk/otel': specifier: '>=1.0.0-0 <2' - version: 1.0.0-beta.6(zod@3.25.76) + version: 1.0.0-beta.6(zod@4.4.3) '@opentelemetry/api': specifier: 1.9.1 version: 1.9.1 @@ -2193,10 +2194,10 @@ importers: version: 8.5.4 ai: specifier: 6.0.116 - version: 6.0.116(zod@3.25.76) + version: 6.0.116(zod@4.4.3) ai-v7: specifier: npm:ai@7.0.0-canary.159 - version: ai@7.0.0-canary.159(zod@3.25.76) + version: ai@7.0.0-canary.159(zod@4.4.3) encoding: specifier: ^0.1.13 version: 0.1.13 @@ -2213,8 +2214,8 @@ importers: specifier: ^2.1.0 version: 2.1.0 zod: - specifier: 3.25.76 - version: 3.25.76 + specifier: 4.4.3 + version: 4.4.3 packages: @@ -2222,31 +2223,31 @@ packages: resolution: {integrity: sha512-BIDaHmCHs6Sr5VUsEkTbbVlAN4GWjg97X9x/IfXyviLtzsXvffui9XIcZugkAi1Ri6FnvI5T5qDGh5YLnSuzRg==} engines: {node: '>=18'} peerDependencies: - zod: ^3.25.76 || ^4.1.8 + zod: 4.4.3 '@ai-sdk/gateway@3.0.66': resolution: {integrity: sha512-SIQ0YY0iMuv+07HLsZ+bB990zUJ6S4ujORAh+Jv1V2KGNn73qQKnGO0JBk+w+Res8YqOFSycwDoWcFlQrVxS4A==} engines: {node: '>=18'} peerDependencies: - zod: ^3.25.76 || ^4.1.8 + zod: 4.4.3 '@ai-sdk/gateway@4.0.0-beta.30': resolution: {integrity: sha512-05tWxCb+3SRj5LBY37nRQ8eUdolO5QvKAfsIAUY3zAxg0jc1cBkQNSpZEVwQUR4uFm/Y5fLJ+MIa+6hkol1//A==} engines: {node: '>=18'} peerDependencies: - zod: ^3.25.76 || ^4.1.8 + zod: 4.4.3 '@ai-sdk/gateway@4.0.0-canary.93': resolution: {integrity: sha512-/d0d/DfJRmdMJJww/ihrYezgKedJhrOCsREuAuSHmd/kVzFB5p2bBuCZH3EBxKFQ8NLtPJpOR+QJxOUmDLD8PA==} engines: {node: '>=22'} peerDependencies: - zod: ^3.25.76 || ^4.1.8 + zod: 4.4.3 '@ai-sdk/openai@3.0.41': resolution: {integrity: sha512-IZ42A+FO+vuEQCVNqlnAPYQnnUpUfdJIwn1BEDOBywiEHa23fw7PahxVtlX9zm3/zMvTW4JKPzWyvAgDu+SQ2A==} engines: {node: '>=18'} peerDependencies: - zod: ^3.25.76 || ^4.1.8 + zod: 4.4.3 '@ai-sdk/otel@1.0.0-beta.6': resolution: {integrity: sha512-K5VikyO3EKQkNk77ew9oMjM8FInKF+WWar599LmP8rQ0x0iB+P/DVS+h6zQvmecxMNPtQOOyt0uDQFx/AA0DGw==} @@ -2256,7 +2257,7 @@ packages: resolution: {integrity: sha512-YHK2rpj++wnLVc9vPGzGFP3Pjeld2MwhKinetA0zKXOoHAT/Jit5O8kZsxcSlJPu9wvcGT1UGZEjZrtO7PfFOQ==} engines: {node: '>=18'} peerDependencies: - zod: ^3.0.0 + zod: 4.4.3 peerDependenciesMeta: zod: optional: true @@ -2265,19 +2266,19 @@ packages: resolution: {integrity: sha512-uhukHaCBvqkwBHkT8C2PrnqKTCoLn3pdHXqtcR9I8ErH+flbzgW4o7VHSNIup9LRu+WBvZIZDQLsx6rwl2tiOA==} engines: {node: '>=18'} peerDependencies: - zod: ^3.25.76 || ^4.1.8 + zod: 4.4.3 '@ai-sdk/provider-utils@5.0.0-beta.9': resolution: {integrity: sha512-Ku/S1TV8ibqlweKQHa22eWT9QvKqR4hkl7KMJ0k7kw64Q7XObh12fJUnK4Mh8q+IR5ZeQGm95SLtx++d7FC6XA==} engines: {node: '>=18'} peerDependencies: - zod: ^3.25.76 || ^4.1.8 + zod: 4.4.3 '@ai-sdk/provider-utils@5.0.0-canary.44': resolution: {integrity: sha512-3h7gp8MEwXACWsW8ZPjzio53ynKq0jTNp4Hwr0BWdychMFzmkRRWyvijhg56JWxye16FU92SqQnfBoe2PN4I2A==} engines: {node: '>=22'} peerDependencies: - zod: ^3.25.76 || ^4.1.8 + zod: 4.4.3 '@ai-sdk/provider@0.0.26': resolution: {integrity: sha512-dQkfBDs2lTYpKM8389oopPdQgIU007GQyCbuPPrV+K6MtSII3HBfE0stUIMXUb44L+LK1t6GXPP7wjSzjO6uKg==} @@ -3149,7 +3150,7 @@ packages: '@conform-to/zod@1.19.4': resolution: {integrity: sha512-g80QtotXLcXWCF86Xou/VpI6i+NBOQxYgLS9YCQYrgV27yilZFJYpgWWBsA8YwodmJVBIYm9RawJba7WnUoddA==} peerDependencies: - zod: ^3.21.0 || ^4.0.0 + zod: 4.4.3 '@connectrpc/connect-node@1.4.0': resolution: {integrity: sha512-0ANnrr6SvsjevsWEgdzHy7BaHkluZyS6s4xNoVt7RBHFR5V/kT9lPokoIbYUOU9JHzdRgTaS3x5595mwUsu15g==} @@ -4607,7 +4608,7 @@ packages: engines: {node: '>=18'} peerDependencies: '@cfworker/json-schema': ^4.1.1 - zod: ^3.25 || ^4.0 + zod: 4.4.3 peerDependenciesMeta: '@cfworker/json-schema': optional: true @@ -4617,7 +4618,7 @@ packages: engines: {node: '>=18'} peerDependencies: '@cfworker/json-schema': ^4.1.1 - zod: ^3.25 || ^4.0 + zod: 4.4.3 peerDependenciesMeta: '@cfworker/json-schema': optional: true @@ -7730,7 +7731,7 @@ packages: '@trigger.dev/platform@1.3.0': resolution: {integrity: sha512-DIJ5Sy4X5XKSHQdYA3GImkb0jdE9I/HTRRRB3c9CFuXjGzhK3rJkEzjIEbaNRjk6yAev67EVTWY8P+D+khorUw==} peerDependencies: - zod: ^3.25.0 || ^4.0.0 + zod: 4.4.3 '@tybys/wasm-util@0.10.3': resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} @@ -8406,19 +8407,19 @@ packages: resolution: {integrity: sha512-7yM+cTmyRLeNIXwt4Vj+mrrJgVQ9RMIW5WO0ydoLoYkewIvsMcvUmqS4j2RJTUXaF1HphwmSKUMQ/HypNRGOmA==} engines: {node: '>=18'} peerDependencies: - zod: ^3.25.76 || ^4.1.8 + zod: 4.4.3 ai@7.0.0-beta.60: resolution: {integrity: sha512-grSKE/9kQtElLCnrhwriTLwXlV+5W8Ljco2KzVc5cY9egK1zwSwVyFQo3L3Al+WVxwvVT6fQjLyEdQpqJHLTrA==} engines: {node: '>=18'} peerDependencies: - zod: ^3.25.76 || ^4.1.8 + zod: 4.4.3 ai@7.0.0-canary.159: resolution: {integrity: sha512-9z8OkZdrISDjS3bguQ/mCYsthmr7mNbB1hTa8cRg8aAp31r1HCxVFqmc+ZXTA0FqhGb8aW46UUcjUuJcwxg5Mw==} engines: {node: '>=22'} peerDependencies: - zod: ^3.25.76 || ^4.1.8 + zod: 4.4.3 ajv-formats@2.1.1: resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} @@ -12613,7 +12614,7 @@ packages: hasBin: true peerDependencies: ws: 8.21.0 - zod: ^3.23.8 + zod: 4.4.3 peerDependenciesMeta: ws: optional: true @@ -13769,7 +13770,7 @@ packages: intl-parse-accept-language: ^1.0.0 is-ip: ^5.0.1 react: 18.3.1 - zod: ^3.22.4 + zod: 4.4.3 peerDependenciesMeta: '@remix-run/cloudflare': optional: true @@ -15603,31 +15604,28 @@ packages: zod-to-json-schema@3.24.5: resolution: {integrity: sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==} peerDependencies: - zod: ^3.24.1 + zod: 4.4.3 zod-to-json-schema@3.24.6: resolution: {integrity: sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==} peerDependencies: - zod: ^3.24.1 + zod: 4.4.3 zod-to-json-schema@3.25.0: resolution: {integrity: sha512-HvWtU2UG41LALjajJrML6uQejQhNJx+JBO9IflpSja4R03iNWfKXrj6W2h7ljuLyc1nKS+9yDyL/9tD1U/yBnQ==} peerDependencies: - zod: ^3.25 || ^4 + zod: 4.4.3 zod-to-json-schema@3.25.1: resolution: {integrity: sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==} peerDependencies: - zod: ^3.25 || ^4 + zod: 4.4.3 - zod-validation-error@1.5.0: - resolution: {integrity: sha512-/7eFkAI4qV0tcxMBB/3+d2c1P6jzzZYdYSlBuAklzMuCrJu5bzJfHS0yVAS87dRHVlhftd6RFJDIvv03JgkSbw==} - engines: {node: '>=16.0.0'} + zod-validation-error@5.0.0: + resolution: {integrity: sha512-hmk+pkyKq7Q71PiWVSDUc3VfpzpvcRHZ3QPw9yEMVvmtCekaMeOHnbr3WbxfrgEnQTv6haGP4cmv0Ojmihzsxw==} + engines: {node: '>=18.0.0'} peerDependencies: - zod: ^3.18.0 - - zod@3.25.76: - resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + zod: 4.4.3 zod@4.4.3: resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} @@ -15637,77 +15635,77 @@ packages: snapshots: - '@ai-sdk/anthropic@3.0.84(zod@3.25.76)': + '@ai-sdk/anthropic@3.0.84(zod@4.4.3)': dependencies: '@ai-sdk/provider': 3.0.10 - '@ai-sdk/provider-utils': 4.0.29(zod@3.25.76) - zod: 3.25.76 + '@ai-sdk/provider-utils': 4.0.29(zod@4.4.3) + zod: 4.4.3 - '@ai-sdk/gateway@3.0.66(zod@3.25.76)': + '@ai-sdk/gateway@3.0.66(zod@4.4.3)': dependencies: '@ai-sdk/provider': 3.0.8 - '@ai-sdk/provider-utils': 4.0.29(zod@3.25.76) + '@ai-sdk/provider-utils': 4.0.29(zod@4.4.3) '@vercel/oidc': 3.1.0 - zod: 3.25.76 + zod: 4.4.3 - '@ai-sdk/gateway@4.0.0-beta.30(zod@3.25.76)': + '@ai-sdk/gateway@4.0.0-beta.30(zod@4.4.3)': dependencies: '@ai-sdk/provider': 4.0.0-beta.5 - '@ai-sdk/provider-utils': 5.0.0-beta.9(zod@3.25.76) + '@ai-sdk/provider-utils': 5.0.0-beta.9(zod@4.4.3) '@vercel/oidc': 3.2.0 - zod: 3.25.76 + zod: 4.4.3 - '@ai-sdk/gateway@4.0.0-canary.93(zod@3.25.76)': + '@ai-sdk/gateway@4.0.0-canary.93(zod@4.4.3)': dependencies: '@ai-sdk/provider': 4.0.0-canary.17 - '@ai-sdk/provider-utils': 5.0.0-canary.44(zod@3.25.76) + '@ai-sdk/provider-utils': 5.0.0-canary.44(zod@4.4.3) '@vercel/oidc': 3.2.0 - zod: 3.25.76 + zod: 4.4.3 - '@ai-sdk/openai@3.0.41(zod@3.25.76)': + '@ai-sdk/openai@3.0.41(zod@4.4.3)': dependencies: '@ai-sdk/provider': 3.0.8 - '@ai-sdk/provider-utils': 4.0.29(zod@3.25.76) - zod: 3.25.76 + '@ai-sdk/provider-utils': 4.0.29(zod@4.4.3) + zod: 4.4.3 - '@ai-sdk/otel@1.0.0-beta.6(zod@3.25.76)': + '@ai-sdk/otel@1.0.0-beta.6(zod@4.4.3)': dependencies: '@ai-sdk/provider': 4.0.0-beta.5 '@opentelemetry/api': 1.9.0 - ai: 7.0.0-beta.60(zod@3.25.76) + ai: 7.0.0-beta.60(zod@4.4.3) transitivePeerDependencies: - zod - '@ai-sdk/provider-utils@1.0.22(zod@3.25.76)': + '@ai-sdk/provider-utils@1.0.22(zod@4.4.3)': dependencies: '@ai-sdk/provider': 0.0.26 eventsource-parser: 1.1.2 nanoid: 3.3.8 secure-json-parse: 2.7.0 optionalDependencies: - zod: 3.25.76 + zod: 4.4.3 - '@ai-sdk/provider-utils@4.0.29(zod@3.25.76)': + '@ai-sdk/provider-utils@4.0.29(zod@4.4.3)': dependencies: '@ai-sdk/provider': 3.0.10 '@standard-schema/spec': 1.1.0 eventsource-parser: 3.1.0 - zod: 3.25.76 + zod: 4.4.3 - '@ai-sdk/provider-utils@5.0.0-beta.9(zod@3.25.76)': + '@ai-sdk/provider-utils@5.0.0-beta.9(zod@4.4.3)': dependencies: '@ai-sdk/provider': 4.0.0-beta.5 '@standard-schema/spec': 1.1.0 eventsource-parser: 3.1.0 - zod: 3.25.76 + zod: 4.4.3 - '@ai-sdk/provider-utils@5.0.0-canary.44(zod@3.25.76)': + '@ai-sdk/provider-utils@5.0.0-canary.44(zod@4.4.3)': dependencies: '@ai-sdk/provider': 4.0.0-canary.17 '@standard-schema/spec': 1.1.0 '@workflow/serde': 4.1.0 eventsource-parser: 3.1.0 - zod: 3.25.76 + zod: 4.4.3 '@ai-sdk/provider@0.0.26': dependencies: @@ -15729,10 +15727,10 @@ snapshots: dependencies: json-schema: 0.4.0 - '@ai-sdk/react@3.0.170(react@18.3.1)(zod@3.25.76)': + '@ai-sdk/react@3.0.170(react@18.3.1)(zod@4.4.3)': dependencies: - '@ai-sdk/provider-utils': 4.0.29(zod@3.25.76) - ai: 6.0.116(zod@3.25.76) + '@ai-sdk/provider-utils': 4.0.29(zod@4.4.3) + ai: 6.0.116(zod@4.4.3) react: 18.3.1 swr: 2.2.5(react@18.3.1) throttleit: 2.1.0 @@ -17821,10 +17819,10 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@conform-to/zod@1.19.4(zod@3.25.76)': + '@conform-to/zod@1.19.4(zod@4.4.3)': dependencies: '@conform-to/dom': 1.19.4 - zod: 3.25.76 + zod: 4.4.3 '@connectrpc/connect-node@1.4.0(@bufbuild/protobuf@1.10.0)(@connectrpc/connect@1.4.0(@bufbuild/protobuf@1.10.0))': dependencies: @@ -18899,7 +18897,7 @@ snapshots: '@microsoft/fetch-event-source@2.0.1': {} - '@modelcontextprotocol/sdk@1.25.2(hono@4.12.25)(supports-color@10.0.0)(zod@3.25.76)': + '@modelcontextprotocol/sdk@1.25.2(hono@4.12.25)(supports-color@10.0.0)(zod@4.4.3)': dependencies: '@hono/node-server': 1.19.9(hono@4.12.25) ajv: 8.18.0 @@ -18915,13 +18913,13 @@ snapshots: json-schema-typed: 8.0.2 pkce-challenge: 5.0.0 raw-body: 3.0.0 - zod: 3.25.76 - zod-to-json-schema: 3.25.0(zod@3.25.76) + zod: 4.4.3 + zod-to-json-schema: 3.25.0(zod@4.4.3) transitivePeerDependencies: - hono - supports-color - '@modelcontextprotocol/sdk@1.26.0(zod@3.25.76)': + '@modelcontextprotocol/sdk@1.26.0(zod@4.4.3)': dependencies: '@hono/node-server': 1.19.9(hono@4.12.25) ajv: 8.18.0 @@ -18938,8 +18936,8 @@ snapshots: json-schema-typed: 8.0.2 pkce-challenge: 5.0.0 raw-body: 3.0.0 - zod: 3.25.76 - zod-to-json-schema: 3.25.1(zod@3.25.76) + zod: 4.4.3 + zod-to-json-schema: 3.25.1(zod@4.4.3) transitivePeerDependencies: - supports-color @@ -22368,9 +22366,9 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@trigger.dev/platform@1.3.0(zod@3.25.76)': + '@trigger.dev/platform@1.3.0(zod@4.4.3)': dependencies: - zod: 3.25.76 + zod: 4.4.3 '@tybys/wasm-util@0.10.3': dependencies: @@ -22889,7 +22887,7 @@ snapshots: '@unkey/error@0.2.0': dependencies: - zod: 3.25.76 + zod: 4.4.3 '@upsetjs/venn.js@2.0.0': optionalDependencies: @@ -22961,8 +22959,8 @@ snapshots: '@vercel/sdk@1.19.1': dependencies: - '@modelcontextprotocol/sdk': 1.26.0(zod@3.25.76) - zod: 3.25.76 + '@modelcontextprotocol/sdk': 1.26.0(zod@4.4.3) + zod: 4.4.3 transitivePeerDependencies: - '@cfworker/json-schema' - supports-color @@ -23197,27 +23195,27 @@ snapshots: ahocorasick@1.0.2: {} - ai@6.0.116(zod@3.25.76): + ai@6.0.116(zod@4.4.3): dependencies: - '@ai-sdk/gateway': 3.0.66(zod@3.25.76) + '@ai-sdk/gateway': 3.0.66(zod@4.4.3) '@ai-sdk/provider': 3.0.8 - '@ai-sdk/provider-utils': 4.0.29(zod@3.25.76) + '@ai-sdk/provider-utils': 4.0.29(zod@4.4.3) '@opentelemetry/api': 1.9.0 - zod: 3.25.76 + zod: 4.4.3 - ai@7.0.0-beta.60(zod@3.25.76): + ai@7.0.0-beta.60(zod@4.4.3): dependencies: - '@ai-sdk/gateway': 4.0.0-beta.30(zod@3.25.76) + '@ai-sdk/gateway': 4.0.0-beta.30(zod@4.4.3) '@ai-sdk/provider': 4.0.0-beta.5 - '@ai-sdk/provider-utils': 5.0.0-beta.9(zod@3.25.76) - zod: 3.25.76 + '@ai-sdk/provider-utils': 5.0.0-beta.9(zod@4.4.3) + zod: 4.4.3 - ai@7.0.0-canary.159(zod@3.25.76): + ai@7.0.0-canary.159(zod@4.4.3): dependencies: - '@ai-sdk/gateway': 4.0.0-canary.93(zod@3.25.76) + '@ai-sdk/gateway': 4.0.0-canary.93(zod@4.4.3) '@ai-sdk/provider': 4.0.0-canary.17 - '@ai-sdk/provider-utils': 5.0.0-canary.44(zod@3.25.76) - zod: 3.25.76 + '@ai-sdk/provider-utils': 5.0.0-canary.44(zod@4.4.3) + zod: 4.4.3 ajv-formats@2.1.1(ajv@8.20.0): optionalDependencies: @@ -23404,9 +23402,9 @@ snapshots: js-yaml: 4.1.1 linear-sum-assignment: 1.0.7 mustache: 4.2.0 - openai: 4.97.0(encoding@0.1.13)(ws@8.21.0(bufferutil@4.0.9))(zod@3.25.76) - zod: 3.25.76 - zod-to-json-schema: 3.24.5(zod@3.25.76) + openai: 4.97.0(encoding@0.1.13)(ws@8.21.0(bufferutil@4.0.9))(zod@4.4.3) + zod: 4.4.3 + zod-to-json-schema: 3.24.5(zod@4.4.3) transitivePeerDependencies: - encoding - ws @@ -25307,7 +25305,7 @@ snapshots: dependencies: require-like: 0.1.2 - evalite@1.0.0-beta.16(ai@6.0.116(zod@3.25.76))(better-sqlite3@11.10.0)(bufferutil@4.0.9): + evalite@1.0.0-beta.16(ai@6.0.116(zod@4.4.3))(better-sqlite3@11.10.0)(bufferutil@4.0.9): dependencies: '@fastify/static': 8.2.0 '@fastify/websocket': 11.2.0(bufferutil@4.0.9) @@ -25324,7 +25322,7 @@ snapshots: table: 6.9.0 tinyrainbow: 3.1.0 optionalDependencies: - ai: 6.0.116(zod@3.25.76) + ai: 6.0.116(zod@4.4.3) better-sqlite3: 11.10.0 transitivePeerDependencies: - bufferutil @@ -28219,7 +28217,7 @@ snapshots: transitivePeerDependencies: - encoding - openai@4.97.0(encoding@0.1.13)(ws@8.21.0(bufferutil@4.0.9))(zod@3.25.76): + openai@4.97.0(encoding@0.1.13)(ws@8.21.0(bufferutil@4.0.9))(zod@4.4.3): dependencies: '@types/node': 24.13.3 '@types/node-fetch': 2.6.12 @@ -28230,7 +28228,7 @@ snapshots: node-fetch: 2.6.12(encoding@0.1.13) optionalDependencies: ws: 8.21.0(bufferutil@4.0.9) - zod: 3.25.76 + zod: 4.4.3 transitivePeerDependencies: - encoding @@ -29561,7 +29559,7 @@ snapshots: '@remix-run/server-runtime': 2.17.5(typescript@6.0.3) react: 18.3.1 - remix-utils@7.7.0(@remix-run/node@2.17.5(typescript@6.0.3))(@remix-run/react@2.17.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@6.0.3))(@remix-run/router@1.23.3(patch_hash=33e2966e9ef36aa09955cec9922c5063227b83429157a71ba226369a08627ac9))(crypto-js@4.2.0)(intl-parse-accept-language@1.0.0)(react@18.3.1)(zod@3.25.76): + remix-utils@7.7.0(@remix-run/node@2.17.5(typescript@6.0.3))(@remix-run/react@2.17.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@6.0.3))(@remix-run/router@1.23.3(patch_hash=33e2966e9ef36aa09955cec9922c5063227b83429157a71ba226369a08627ac9))(crypto-js@4.2.0)(intl-parse-accept-language@1.0.0)(react@18.3.1)(zod@4.4.3): dependencies: type-fest: 4.33.0 optionalDependencies: @@ -29571,7 +29569,7 @@ snapshots: crypto-js: 4.2.0 intl-parse-accept-language: 1.0.0 react: 18.3.1 - zod: 3.25.76 + zod: 4.4.3 remove-accents@0.5.0: {} @@ -31688,29 +31686,27 @@ snapshots: zod-error@1.5.0: dependencies: - zod: 3.25.76 + zod: 4.4.3 - zod-to-json-schema@3.24.5(zod@3.25.76): + zod-to-json-schema@3.24.5(zod@4.4.3): dependencies: - zod: 3.25.76 + zod: 4.4.3 - zod-to-json-schema@3.24.6(zod@3.25.76): + zod-to-json-schema@3.24.6(zod@4.4.3): dependencies: - zod: 3.25.76 + zod: 4.4.3 - zod-to-json-schema@3.25.0(zod@3.25.76): + zod-to-json-schema@3.25.0(zod@4.4.3): dependencies: - zod: 3.25.76 + zod: 4.4.3 - zod-to-json-schema@3.25.1(zod@3.25.76): + zod-to-json-schema@3.25.1(zod@4.4.3): dependencies: - zod: 3.25.76 + zod: 4.4.3 - zod-validation-error@1.5.0(zod@3.25.76): + zod-validation-error@5.0.0(zod@4.4.3): dependencies: - zod: 3.25.76 - - zod@3.25.76: {} + zod: 4.4.3 zod@4.4.3: {} From 750070aecb8766216ec61d878bab4805a7ab4924 Mon Sep 17 00:00:00 2001 From: Chris Arderne Date: Mon, 3 Aug 2026 15:46:11 +0100 Subject: [PATCH 08/21] fixes --- .../app/routes/confirm-basic-details.tsx | 5 ++++- apps/webapp/app/routes/login._index/route.tsx | 2 +- apps/webapp/app/routes/login.sso/route.tsx | 2 +- packages/schema-to-json/package.json | 4 ++-- packages/schema-to-json/src/index.ts | 22 +++++++++---------- pnpm-lock.yaml | 13 ++--------- 6 files changed, 21 insertions(+), 27 deletions(-) diff --git a/apps/webapp/app/routes/confirm-basic-details.tsx b/apps/webapp/app/routes/confirm-basic-details.tsx index c15d5991705..efea8ac4ad0 100644 --- a/apps/webapp/app/routes/confirm-basic-details.tsx +++ b/apps/webapp/app/routes/confirm-basic-details.tsx @@ -316,7 +316,10 @@ export default function Page() { ) : ( <> - + )} diff --git a/apps/webapp/app/routes/login._index/route.tsx b/apps/webapp/app/routes/login._index/route.tsx index 81c5d26d30b..a3d402078e8 100644 --- a/apps/webapp/app/routes/login._index/route.tsx +++ b/apps/webapp/app/routes/login._index/route.tsx @@ -1,5 +1,5 @@ import { getFormProps, getInputProps, useForm } from "@conform-to/react"; -import { parseWithZod } from "@conform-to/zod"; +import { parseWithZod } from "@conform-to/zod/v4"; import { EnvelopeIcon, LockClosedIcon } from "@heroicons/react/20/solid"; import type { LoaderFunctionArgs, MetaFunction } from "@remix-run/node"; import { Form, useNavigation } from "@remix-run/react"; diff --git a/apps/webapp/app/routes/login.sso/route.tsx b/apps/webapp/app/routes/login.sso/route.tsx index d03b7de2893..f3c3b0a4188 100644 --- a/apps/webapp/app/routes/login.sso/route.tsx +++ b/apps/webapp/app/routes/login.sso/route.tsx @@ -1,5 +1,5 @@ import { getFormProps, getInputProps, useForm } from "@conform-to/react"; -import { parseWithZod } from "@conform-to/zod"; +import { parseWithZod } from "@conform-to/zod/v4"; import { ArrowLeftIcon, ArrowUpRightIcon } from "@heroicons/react/20/solid"; import type { LoaderFunctionArgs, MetaFunction } from "@remix-run/node"; import { Form, useNavigation } from "@remix-run/react"; diff --git a/packages/schema-to-json/package.json b/packages/schema-to-json/package.json index 0a590345abc..cc014e30706 100644 --- a/packages/schema-to-json/package.json +++ b/packages/schema-to-json/package.json @@ -42,9 +42,9 @@ }, "dependencies": { "@trigger.dev/core": "workspace:*", - "zod-to-json-schema": "^3.24.0", + "zod-to-json-schema": "^3.25.0", "@sodaru/yup-to-json-schema": "^2", - "zod": "3.25.76", + "zod": "4.4.3", "effect": "^3" }, "devDependencies": { diff --git a/packages/schema-to-json/src/index.ts b/packages/schema-to-json/src/index.ts index a02eef07aa3..1cb42ef2d79 100644 --- a/packages/schema-to-json/src/index.ts +++ b/packages/schema-to-json/src/index.ts @@ -43,26 +43,26 @@ export function schemaToJsonSchema( ): ConversionResult | undefined { const parser = schema as any; - // Check if schema has a built-in toJsonSchema method (e.g., ArkType, Zod 4) - if (typeof parser.toJsonSchema === "function") { - try { - const jsonSchema = parser.toJsonSchema(); + if (isZodSchema(parser)) { + const jsonSchema = convertZodSchema(parser, options); + if (jsonSchema) { return { - jsonSchema, + jsonSchema: jsonSchema, }; - } catch (_error) { - // If toJsonSchema fails, continue to other checks } } - if (isZodSchema(parser)) { - const jsonSchema = convertZodSchema(parser, options); + // Check if schema has a built-in toJsonSchema method (e.g., ArkType) + if (typeof parser.toJsonSchema === "function") { + try { + const jsonSchema = parser.toJsonSchema(); - if (jsonSchema) { return { - jsonSchema: jsonSchema, + jsonSchema, }; + } catch (_error) { + // If toJsonSchema fails, continue to other checks } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4ed4f2765d1..93bc82c994f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2101,8 +2101,8 @@ importers: specifier: 4.4.3 version: 4.4.3 zod-to-json-schema: - specifier: ^3.24.0 - version: 3.24.6(zod@4.4.3) + specifier: ^3.25.0 + version: 3.25.1(zod@4.4.3) devDependencies: '@arethetypeswrong/cli': specifier: ^0.18.5 @@ -15606,11 +15606,6 @@ packages: peerDependencies: zod: 4.4.3 - zod-to-json-schema@3.24.6: - resolution: {integrity: sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==} - peerDependencies: - zod: 4.4.3 - zod-to-json-schema@3.25.0: resolution: {integrity: sha512-HvWtU2UG41LALjajJrML6uQejQhNJx+JBO9IflpSja4R03iNWfKXrj6W2h7ljuLyc1nKS+9yDyL/9tD1U/yBnQ==} peerDependencies: @@ -31692,10 +31687,6 @@ snapshots: dependencies: zod: 4.4.3 - zod-to-json-schema@3.24.6(zod@4.4.3): - dependencies: - zod: 4.4.3 - zod-to-json-schema@3.25.0(zod@4.4.3): dependencies: zod: 4.4.3 From fbf3bdc20deb19de2ac80f6572259f66af662463 Mon Sep 17 00:00:00 2001 From: Chris Arderne Date: Mon, 3 Aug 2026 16:17:24 +0100 Subject: [PATCH 09/21] fix test --- apps/webapp/test/routesBatchGetReplicaLag.guard.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/webapp/test/routesBatchGetReplicaLag.guard.test.ts b/apps/webapp/test/routesBatchGetReplicaLag.guard.test.ts index 6fe7a646708..e851dea2053 100644 --- a/apps/webapp/test/routesBatchGetReplicaLag.guard.test.ts +++ b/apps/webapp/test/routesBatchGetReplicaLag.guard.test.ts @@ -331,7 +331,7 @@ describe("routes-batch-get callers under a lagging replica", () => { holder.environment = authEnv(seed); holder.cachedRequest = { id: batchId }; - const body = JSON.stringify({ items: [{ task: "my-task" }] }); + const body = JSON.stringify({ items: [{ task: "my-task", payload: null }] }); const res = (await actionTasksBatch({ request: new Request("http://localhost/api/v2/tasks/batch", { method: "POST", From b017f3f156d75b5cb571290b8c77316f8e17942b Mon Sep 17 00:00:00 2001 From: Chris Arderne Date: Mon, 3 Aug 2026 16:43:09 +0100 Subject: [PATCH 10/21] bump platform dep --- apps/webapp/package.json | 2 +- pnpm-lock.yaml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/webapp/package.json b/apps/webapp/package.json index 21a28f7a54e..44e78de653a 100644 --- a/apps/webapp/package.json +++ b/apps/webapp/package.json @@ -120,7 +120,7 @@ "@trigger.dev/core": "workspace:*", "@trigger.dev/database": "workspace:*", "@trigger.dev/otlp-importer": "workspace:*", - "@trigger.dev/platform": "1.3.0", + "@trigger.dev/platform": "1.3.1", "@trigger.dev/plugins": "workspace:*", "@trigger.dev/rbac": "workspace:*", "@trigger.dev/redis-worker": "workspace:*", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 93bc82c994f..758dd3cd2d0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -490,8 +490,8 @@ importers: specifier: workspace:* version: link:../../internal-packages/otlp-importer '@trigger.dev/platform': - specifier: 1.3.0 - version: 1.3.0(zod@4.4.3) + specifier: 1.3.1 + version: 1.3.1(zod@4.4.3) '@trigger.dev/plugins': specifier: workspace:* version: link:../../packages/plugins @@ -7728,8 +7728,8 @@ packages: react: 18.3.1 react-dom: 18.3.1 - '@trigger.dev/platform@1.3.0': - resolution: {integrity: sha512-DIJ5Sy4X5XKSHQdYA3GImkb0jdE9I/HTRRRB3c9CFuXjGzhK3rJkEzjIEbaNRjk6yAev67EVTWY8P+D+khorUw==} + '@trigger.dev/platform@1.3.1': + resolution: {integrity: sha512-eCmoeilJyat6APofjHnyiJzOYobHbukbmexDpZVbQlqAkJaXKsK/f07Ijuw8Cyx3kTj8Hc24fj2vMHFPwcE9VA==} peerDependencies: zod: 4.4.3 @@ -22361,7 +22361,7 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@trigger.dev/platform@1.3.0(zod@4.4.3)': + '@trigger.dev/platform@1.3.1(zod@4.4.3)': dependencies: zod: 4.4.3 From 55475955414334fcaa224aef316404889ca9b446 Mon Sep 17 00:00:00 2001 From: Chris Arderne Date: Mon, 3 Aug 2026 16:47:26 +0100 Subject: [PATCH 11/21] fix(webapp): keep feedback dialog open for field errors --- apps/webapp/app/components/Feedback.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/webapp/app/components/Feedback.tsx b/apps/webapp/app/components/Feedback.tsx index fc60bdad1b7..d7204dde41a 100644 --- a/apps/webapp/app/components/Feedback.tsx +++ b/apps/webapp/app/components/Feedback.tsx @@ -66,11 +66,11 @@ export function Feedback({ if ( navigation.formAction === "/resources/feedback" && navigation.state === "loading" && - (form.errors === undefined || form.errors.length === 0) + Object.keys(form.allErrors).length === 0 ) { setOpen(false); } - }, [navigation, form]); + }, [navigation.formAction, navigation.state, form.allErrors]); // Handle URL param functionality useEffect(() => { From f1edc1ccc18cef9ac9d6f8d193d394fb28cc38b5 Mon Sep 17 00:00:00 2001 From: Chris Arderne Date: Mon, 3 Aug 2026 16:54:05 +0100 Subject: [PATCH 12/21] fix schema --- apps/webapp/app/services/billingLimit.schemas.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/webapp/app/services/billingLimit.schemas.ts b/apps/webapp/app/services/billingLimit.schemas.ts index 6628e5cb22c..9153b1fce85 100644 --- a/apps/webapp/app/services/billingLimit.schemas.ts +++ b/apps/webapp/app/services/billingLimit.schemas.ts @@ -175,7 +175,7 @@ export type BillingLimitPageData = BillingLimitResult & { currentSpendCents: number; }; -/** Bridge webapp Zod schemas to BillingClient.fetch (separate Zod type instances). */ -export function asPlatformSchema(schema: z.ZodTypeAny) { - return schema as unknown as Parameters[1]; +/** Preserve a concrete schema type when passing it to BillingClient.fetch. */ +export function asPlatformSchema(schema: TSchema): TSchema { + return schema; } From e1e977299b7fa81098f3a2fe0f90410b7bfe7681 Mon Sep 17 00:00:00 2001 From: Chris Arderne Date: Mon, 3 Aug 2026 16:59:56 +0100 Subject: [PATCH 13/21] lint --- apps/webapp/app/services/billingLimit.schemas.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/webapp/app/services/billingLimit.schemas.ts b/apps/webapp/app/services/billingLimit.schemas.ts index 9153b1fce85..8ffdca3fd57 100644 --- a/apps/webapp/app/services/billingLimit.schemas.ts +++ b/apps/webapp/app/services/billingLimit.schemas.ts @@ -1,4 +1,3 @@ -import type { BillingClient } from "@trigger.dev/platform"; import { z } from "zod"; /** From 88ac58892583a8898b13d05569ce33a87d523e02 Mon Sep 17 00:00:00 2001 From: Chris Arderne Date: Mon, 3 Aug 2026 22:28:42 +0100 Subject: [PATCH 14/21] fix: preserve zod v4 configuration behavior --- AGENTS.md | 2 +- .../route.tsx | 12 +++++++----- apps/webapp/app/services/platform.v3.server.ts | 2 +- internal-packages/dashboard-agent/package.json | 2 +- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index cc947c7a57d..e459a74c9c6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -108,7 +108,7 @@ When modifying only server components (`apps/webapp/`, `apps/supervisor/`, etc.) ## Dependency Pinning -Zod is pinned to a single version across the entire monorepo (currently `3.25.76`). When adding zod to a new or existing package, use the **exact same version** as the rest of the repo - never a different version or a range. Mismatched zod versions cause runtime type incompatibilities (e.g., schemas from one package can't be used as body validators in another). +Zod is pinned to a single version across the entire monorepo (currently `4.4.3`). When adding zod to a new or existing package, use the **exact same version** as the rest of the repo - never a different version or a range. Mismatched zod versions cause runtime type incompatibilities (e.g., schemas from one package can't be used as body validators in another). ## Architecture Overview diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables.new/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables.new/route.tsx index d3402165808..1c397e3eddf 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables.new/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables.new/route.tsx @@ -247,12 +247,14 @@ export default function Page() { const [selectedEnvironmentIds, setSelectedEnvironmentIds] = useState>(new Set()); const [selectedBranchId, setSelectedBranchId] = useState(undefined); - const branchEnvironments = environments.filter((env) => env.branchName); - const nonBranchEnvironments = environments.filter((env) => !env.branchName); - const selectedEnvironments = environments.filter((env) => selectedEnvironmentIds.has(env.id)); - const previewIsSelected = selectedEnvironments.some( - (env) => env.branchName !== null || env.type === "PREVIEW" + // TODO: we only support branch-specific env vars for Preview environments. + // This keeps setting consistent env vars across Dev/Staging/Prod easier. + const branchEnvironments = environments.filter( + (env) => env.type === "PREVIEW" && env.parentEnvironmentId !== null ); + const nonBranchEnvironments = environments.filter((env) => env.parentEnvironmentId === null); + const selectedEnvironments = environments.filter((env) => selectedEnvironmentIds.has(env.id)); + const previewIsSelected = selectedEnvironments.some((env) => env.type === "PREVIEW"); const isLoading = navigation.state !== "idle" && navigation.formMethod === "post"; diff --git a/apps/webapp/app/services/platform.v3.server.ts b/apps/webapp/app/services/platform.v3.server.ts index f8b524e3cdd..a533cafaf42 100644 --- a/apps/webapp/app/services/platform.v3.server.ts +++ b/apps/webapp/app/services/platform.v3.server.ts @@ -242,7 +242,7 @@ const MachineOverrideValues = z.object({ }); type MachineOverrideValues = z.infer; -const MachineOverrides = z.record(MachinePresetName, MachineOverrideValues.partial()); +const MachineOverrides = z.partialRecord(MachinePresetName, MachineOverrideValues.partial()); type MachineOverrides = z.infer; const MachinePresetOverrides = z.object({ diff --git a/internal-packages/dashboard-agent/package.json b/internal-packages/dashboard-agent/package.json index c0776e25f11..3bfe5f3f4a6 100644 --- a/internal-packages/dashboard-agent/package.json +++ b/internal-packages/dashboard-agent/package.json @@ -14,7 +14,7 @@ "@internal/dashboard-agent-db": "workspace:*", "@trigger.dev/sdk": "workspace:*", "ai": "^6.0.116", - "zod": "3.25.76" + "zod": "4.4.3" }, "devDependencies": { "@ai-sdk/provider": "3.0.8", From db3da91edb06226eef4234fdedf3e0b4c053a77c Mon Sep 17 00:00:00 2001 From: Chris Arderne Date: Tue, 4 Aug 2026 07:05:16 +0100 Subject: [PATCH 15/21] fix: declare zod peer dependencies --- .changeset/zod-4-support.md | 3 +++ packages/build/package.json | 6 +++++- packages/react-hooks/package.json | 6 ++++-- packages/rsc/package.json | 6 +++++- pnpm-lock.yaml | 9 +++++++++ 5 files changed, 26 insertions(+), 4 deletions(-) diff --git a/.changeset/zod-4-support.md b/.changeset/zod-4-support.md index fd739c5be76..4849b8624b4 100644 --- a/.changeset/zod-4-support.md +++ b/.changeset/zod-4-support.md @@ -1,5 +1,8 @@ --- +"@trigger.dev/build": minor "@trigger.dev/core": minor +"@trigger.dev/react-hooks": minor +"@trigger.dev/rsc": minor "@trigger.dev/sdk": minor "trigger.dev": minor "@trigger.dev/redis-worker": minor diff --git a/packages/build/package.json b/packages/build/package.json index c55ffbb687e..6d051d4741a 100644 --- a/packages/build/package.json +++ b/packages/build/package.json @@ -92,7 +92,11 @@ "esbuild": "^0.23.0", "rimraf": "6.0.1", "tshy": "^3.0.2", - "tsx": "4.17.0" + "tsx": "4.17.0", + "zod": "4.4.3" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" }, "engines": { "node": ">=18.20.0" diff --git a/packages/react-hooks/package.json b/packages/react-hooks/package.json index cc8bc3dd300..b7039450a88 100644 --- a/packages/react-hooks/package.json +++ b/packages/react-hooks/package.json @@ -46,11 +46,13 @@ "@types/react-dom": "*", "rimraf": "^6.0.1", "tshy": "^3.0.2", - "tsx": "4.17.0" + "tsx": "4.17.0", + "zod": "4.4.3" }, "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^18.0 || ^19.0 || ^19.0.0-rc" + "react-dom": "^18.0 || ^19.0 || ^19.0.0-rc", + "zod": "^3.25.0 || ^4.0.0" }, "engines": { "node": ">=18.20.0" diff --git a/packages/rsc/package.json b/packages/rsc/package.json index eb6396223dc..c0232aeefae 100644 --- a/packages/rsc/package.json +++ b/packages/rsc/package.json @@ -50,7 +50,11 @@ "@types/react-dom": "*", "rimraf": "^6.0.1", "tshy": "^3.0.2", - "tsx": "4.17.0" + "tsx": "4.17.0", + "zod": "4.4.3" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" }, "engines": { "node": ">=18.20.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 758dd3cd2d0..5be7a7cf6bd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1496,6 +1496,9 @@ importers: tsx: specifier: 4.17.0 version: 4.17.0 + zod: + specifier: 4.4.3 + version: 4.4.3 packages/cli-v3: dependencies: @@ -1996,6 +1999,9 @@ importers: tsx: specifier: 4.17.0 version: 4.17.0 + zod: + specifier: 4.4.3 + version: 4.4.3 packages/redis-worker: dependencies: @@ -2085,6 +2091,9 @@ importers: tsx: specifier: 4.17.0 version: 4.17.0 + zod: + specifier: 4.4.3 + version: 4.4.3 packages/schema-to-json: dependencies: From c05c5ca93402eef679f63449a6711942976d0d33 Mon Sep 17 00:00:00 2001 From: Chris Arderne Date: Tue, 4 Aug 2026 08:01:11 +0100 Subject: [PATCH 16/21] fix: use zod-error v2 with zod 4 --- apps/webapp/package.json | 2 +- internal-packages/clickhouse/package.json | 2 +- packages/core/package.json | 2 +- pnpm-lock.yaml | 20 +++++++++++--------- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/apps/webapp/package.json b/apps/webapp/package.json index 44e78de653a..0a43858a722 100644 --- a/apps/webapp/package.json +++ b/apps/webapp/package.json @@ -217,7 +217,7 @@ "uuid": "^14.0.0", "ws": "^8.11.0", "zod": "4.4.3", - "zod-error": "1.5.0", + "zod-error": "2.0.0", "zod-validation-error": "^5.0.0" }, "devDependencies": { diff --git a/internal-packages/clickhouse/package.json b/internal-packages/clickhouse/package.json index c3f87cf62dc..719b979ba90 100644 --- a/internal-packages/clickhouse/package.json +++ b/internal-packages/clickhouse/package.json @@ -11,7 +11,7 @@ "@internal/tsql": "workspace:*", "@trigger.dev/core": "workspace:*", "zod": "4.4.3", - "zod-error": "1.5.0" + "zod-error": "2.0.0" }, "devDependencies": { "@internal/testcontainers": "workspace:*", diff --git a/packages/core/package.json b/packages/core/package.json index 1003f7514b8..43b7188352a 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -225,7 +225,7 @@ "std-env": "^3.8.1", "tinyexec": "^0.3.2", "uncrypto": "^0.1.3", - "zod-error": "1.5.0", + "zod-error": "2.0.0", "zod-validation-error": "^5.0.0" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5be7a7cf6bd..ff054afc53e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -781,8 +781,8 @@ importers: specifier: 4.4.3 version: 4.4.3 zod-error: - specifier: 1.5.0 - version: 1.5.0 + specifier: 2.0.0 + version: 2.0.0(zod@4.4.3) zod-validation-error: specifier: ^5.0.0 version: 5.0.0(zod@4.4.3) @@ -979,8 +979,8 @@ importers: specifier: 4.4.3 version: 4.4.3 zod-error: - specifier: 1.5.0 - version: 1.5.0 + specifier: 2.0.0 + version: 2.0.0(zod@4.4.3) devDependencies: '@internal/testcontainers': specifier: workspace:* @@ -1849,8 +1849,8 @@ importers: specifier: ^0.1.3 version: 0.1.3 zod-error: - specifier: 1.5.0 - version: 1.5.0 + specifier: 2.0.0 + version: 2.0.0(zod@4.4.3) zod-validation-error: specifier: ^5.0.0 version: 5.0.0(zod@4.4.3) @@ -15607,8 +15607,10 @@ packages: resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==} engines: {node: '>= 14'} - zod-error@1.5.0: - resolution: {integrity: sha512-zzopKZ/skI9iXpqCEPj+iLCKl9b88E43ehcU+sbRoHuwGd9F1IDVGQ70TyO6kmfiRL1g4IXkjsXK+g1gLYl4WQ==} + zod-error@2.0.0: + resolution: {integrity: sha512-ivSoPm7h5SEGG8GUvQ7YTwrnEx36qNmUIU5hSIGcqcdl7+ZFoTrYXAdQEs3KP/rkihrrR9REEdOeGU+fWdMoNw==} + peerDependencies: + zod: 4.4.3 zod-to-json-schema@3.24.5: resolution: {integrity: sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==} @@ -31688,7 +31690,7 @@ snapshots: compress-commons: 6.0.2 readable-stream: 4.7.0 - zod-error@1.5.0: + zod-error@2.0.0(zod@4.4.3): dependencies: zod: 4.4.3 From d00af71bdd100295420199461887db6192be0686 Mon Sep 17 00:00:00 2001 From: Chris Arderne Date: Tue, 4 Aug 2026 10:46:39 +0100 Subject: [PATCH 17/21] fix: preserve empty task payloads and validation messages --- apps/webapp/app/routes/account._index/route.tsx | 2 +- apps/webapp/app/routes/account.tokens/route.tsx | 2 +- packages/core/src/v3/schemas/api-type.test.ts | 8 ++++++++ packages/core/src/v3/schemas/api.ts | 4 ++-- packages/core/src/v3/schemas/batchItemNDJSON.test.ts | 9 +++++++++ 5 files changed, 21 insertions(+), 4 deletions(-) diff --git a/apps/webapp/app/routes/account._index/route.tsx b/apps/webapp/app/routes/account._index/route.tsx index 03da2f9987a..94969a30239 100644 --- a/apps/webapp/app/routes/account._index/route.tsx +++ b/apps/webapp/app/routes/account._index/route.tsx @@ -92,7 +92,7 @@ function createSchema( name: z .string({ error: "You must enter a name" }) .min(2, "Your name must be at least 2 characters long") - .max(50), + .max(50, "Your name must be 50 characters or less"), email: emailSchema.pipe( z.string().superRefine((email, ctx) => { if (constraints.isEmailUnique === undefined) { diff --git a/apps/webapp/app/routes/account.tokens/route.tsx b/apps/webapp/app/routes/account.tokens/route.tsx index 3889cbdf592..63baccf8f2b 100644 --- a/apps/webapp/app/routes/account.tokens/route.tsx +++ b/apps/webapp/app/routes/account.tokens/route.tsx @@ -170,7 +170,7 @@ const CreateTokenSchema = z.discriminatedUnion("action", [ tokenName: z .string({ error: "You must enter a name" }) .min(2, "Your name must be at least 2 characters long") - .max(50), + .max(50, "Your name must be 50 characters or less"), // Optional — when no RBAC plugin is installed the UI hides the // dropdown and submits no roleId; the action passes that through // and createPersonalAccessToken just doesn't write a TokenRole. diff --git a/packages/core/src/v3/schemas/api-type.test.ts b/packages/core/src/v3/schemas/api-type.test.ts index 39fb3131ce1..73c697969cd 100644 --- a/packages/core/src/v3/schemas/api-type.test.ts +++ b/packages/core/src/v3/schemas/api-type.test.ts @@ -141,6 +141,14 @@ describe("InitializeDeploymentRequestBody", () => { }); describe("TriggerTaskRequestBody", () => { + it("accepts a missing payload", () => { + const result = TriggerTaskRequestBody.safeParse({ + options: {}, + }); + + expect(result.success).toBe(true); + }); + it("accepts application/store payload as a non-empty string", () => { const result = TriggerTaskRequestBody.safeParse({ payload: "packets/payloads/file.json", diff --git a/packages/core/src/v3/schemas/api.ts b/packages/core/src/v3/schemas/api.ts index 245351bf0c5..c16521270cc 100644 --- a/packages/core/src/v3/schemas/api.ts +++ b/packages/core/src/v3/schemas/api.ts @@ -202,7 +202,7 @@ const ConcurrencyKeySchema = z.union([z.string(), z.number()]).transform((value) export const TriggerTaskRequestBody = z .object({ - payload: z.any(), + payload: z.any().optional(), context: z.any().optional(), options: z .object({ @@ -312,7 +312,7 @@ export type BatchTriggerTaskRequestBody = z.infer { }); describe("BatchTriggerTaskItem", () => { + it("accepts a missing payload", () => { + const result = BatchTriggerTaskItem.safeParse({ + task: "user-workflow-tick", + options: {}, + }); + + expect(result.success).toBe(true); + }); + it("coerces a numeric concurrencyKey to a string", () => { const result = BatchTriggerTaskItem.safeParse({ task: "user-workflow-tick", From d884c03ca3085793e5b22b189280c000a7c4bd26 Mon Sep 17 00:00:00 2001 From: Chris Arderne Date: Tue, 4 Aug 2026 14:13:34 +0100 Subject: [PATCH 18/21] improve cross-version zod support --- .changeset/zod-4-support.md | 4 +- packages/build/package.json | 6 +- packages/core/package.json | 7 +- packages/core/src/schemas/eventFilter.ts | 2 +- packages/core/src/schemas/json.ts | 2 +- packages/core/src/v3/apiClient/core.ts | 123 +++++++++++------- packages/core/src/v3/apiClient/index.ts | 2 +- packages/core/src/v3/apiClient/stream.ts | 15 ++- .../src/v3/apiClient/zodCompatibility.test.ts | 65 +++++++++ packages/core/src/v3/errors.ts | 2 +- packages/core/src/v3/index.ts | 3 + .../src/v3/runEngineWorker/supervisor/http.ts | 12 +- .../v3/runEngineWorker/supervisor/schemas.ts | 5 +- .../src/v3/runEngineWorker/workload/http.ts | 2 +- .../v3/runEngineWorker/workload/schemas.ts | 5 +- packages/core/src/v3/schemas/api.ts | 11 +- packages/core/src/v3/schemas/build.ts | 2 +- packages/core/src/v3/schemas/checkpoints.ts | 2 +- packages/core/src/v3/schemas/common.ts | 9 +- packages/core/src/v3/schemas/config.ts | 2 +- packages/core/src/v3/schemas/errors.ts | 2 +- packages/core/src/v3/schemas/eventFilter.ts | 2 +- packages/core/src/v3/schemas/fetch.ts | 5 +- packages/core/src/v3/schemas/messages.ts | 2 +- packages/core/src/v3/schemas/openTelemetry.ts | 2 +- packages/core/src/v3/schemas/query.ts | 5 +- packages/core/src/v3/schemas/queues.ts | 2 +- packages/core/src/v3/schemas/resources.ts | 2 +- packages/core/src/v3/schemas/runEngine.ts | 5 +- packages/core/src/v3/schemas/schemas.ts | 5 +- packages/core/src/v3/schemas/style.ts | 2 +- packages/core/src/v3/schemas/tokens.ts | 2 +- packages/core/src/v3/schemas/warmStart.ts | 2 +- packages/core/src/v3/schemas/webhooks.ts | 5 +- packages/core/src/v3/serverOnly/httpServer.ts | 41 +++--- .../src/v3/sessionStreams/chatSnapshot.ts | 2 +- packages/core/src/v3/types/schemas.test.ts | 39 ++++++ packages/core/src/v3/types/schemas.ts | 14 +- packages/core/src/v3/types/tools.ts | 18 ++- packages/core/src/v3/utils/ioSerialization.ts | 2 +- packages/core/src/v3/utils/zod.ts | 16 +++ .../core/src/v3/workloadDeploymentToken.ts | 2 +- packages/core/src/v3/zodIpc.ts | 25 ++-- packages/core/src/v3/zodMessageHandler.ts | 32 +++-- packages/core/src/v3/zodSocket.ts | 32 +++-- packages/react-hooks/package.json | 6 +- packages/redis-worker/package.json | 6 +- packages/redis-worker/src/fair-queue/index.ts | 44 ++++--- .../src/fair-queue/tests/fairQueue.test.ts | 2 +- .../fair-queue/tests/raceConditions.test.ts | 2 +- .../fair-queue/tests/tenantDispatch.test.ts | 2 +- packages/redis-worker/src/fair-queue/types.ts | 10 +- .../redis-worker/src/mollifier/schemas.ts | 2 +- packages/redis-worker/src/queue.test.ts | 2 +- packages/redis-worker/src/queue.ts | 11 +- packages/redis-worker/src/worker.test.ts | 2 +- packages/redis-worker/src/worker.ts | 18 ++- packages/rsc/package.json | 6 +- packages/schema-to-json/package.json | 3 +- packages/schema-to-json/src/index.ts | 20 ++- packages/schema-to-json/tests/index.test.ts | 1 + packages/trigger-sdk/package.json | 2 +- pnpm-lock.yaml | 24 +--- 63 files changed, 459 insertions(+), 251 deletions(-) create mode 100644 packages/core/src/v3/apiClient/zodCompatibility.test.ts create mode 100644 packages/core/src/v3/types/schemas.test.ts create mode 100644 packages/core/src/v3/utils/zod.ts diff --git a/.changeset/zod-4-support.md b/.changeset/zod-4-support.md index 4849b8624b4..ef90f6396e3 100644 --- a/.changeset/zod-4-support.md +++ b/.changeset/zod-4-support.md @@ -9,6 +9,6 @@ "@trigger.dev/schema-to-json": minor --- -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. +Add Zod 4 support. You can now use Zod 3.25.76+ or Zod 4. -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). +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. diff --git a/packages/build/package.json b/packages/build/package.json index 6d051d4741a..c55ffbb687e 100644 --- a/packages/build/package.json +++ b/packages/build/package.json @@ -92,11 +92,7 @@ "esbuild": "^0.23.0", "rimraf": "6.0.1", "tshy": "^3.0.2", - "tsx": "4.17.0", - "zod": "4.4.3" - }, - "peerDependencies": { - "zod": "^3.25.0 || ^4.0.0" + "tsx": "4.17.0" }, "engines": { "node": ">=18.20.0" diff --git a/packages/core/package.json b/packages/core/package.json index 43b7188352a..7f941cf573e 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -225,7 +225,7 @@ "std-env": "^3.8.1", "tinyexec": "^0.3.2", "uncrypto": "^0.1.3", - "zod-error": "2.0.0", + "zod": "^3.25.76 || ^4.0.0", "zod-validation-error": "^5.0.0" }, "devDependencies": { @@ -244,8 +244,7 @@ "superjson": "^2.2.1", "ts-essentials": "10.0.1", "tshy": "^3.0.2", - "tsx": "4.17.0", - "zod": "4.4.3" + "tsx": "4.17.0" }, "engines": { "node": ">=18.20.0" @@ -665,6 +664,6 @@ "types": "./dist/commonjs/index.d.ts", "module": "./dist/esm/index.js", "peerDependencies": { - "zod": "^3.25.0 || ^4.0.0" + "zod": "^3.25.76 || ^4.0.0" } } diff --git a/packages/core/src/schemas/eventFilter.ts b/packages/core/src/schemas/eventFilter.ts index 25cf03b45e7..77aa40867ff 100644 --- a/packages/core/src/schemas/eventFilter.ts +++ b/packages/core/src/schemas/eventFilter.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; export const stringPatternMatchers = [ z.object({ diff --git a/packages/core/src/schemas/json.ts b/packages/core/src/schemas/json.ts index 985c3073a39..672eb44cc5f 100644 --- a/packages/core/src/schemas/json.ts +++ b/packages/core/src/schemas/json.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; const LiteralSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]); type Literal = z.infer; diff --git a/packages/core/src/v3/apiClient/core.ts b/packages/core/src/v3/apiClient/core.ts index c6425e7ee37..c6f33719847 100644 --- a/packages/core/src/v3/apiClient/core.ts +++ b/packages/core/src/v3/apiClient/core.ts @@ -1,5 +1,6 @@ -import { z } from "zod"; -import { fromZodError, ValidationError } from "zod-validation-error"; +import { z as z3 } from "zod/v3"; +import { z } from "zod/v4"; +import { fromZodError, ValidationError } from "zod-validation-error/v4"; import type { RetryOptions } from "../schemas/index.js"; import { calculateNextRetryDelay } from "../utils/retries.js"; import { ApiConnectionError, ApiError, ApiSchemaValidationError } from "./errors.js"; @@ -12,6 +13,10 @@ import { SemanticInternalAttributes } from "../semanticInternalAttributes.js"; import type { TriggerTracer } from "../tracer.js"; import { randomUUID } from "../utils/crypto.js"; import { accessoryAttributes } from "../utils/styleAttributes.js"; +import type { + AnyZodSchema, + inferZodSchemaOutput, +} from "../types/schemas.js"; import type { CursorPageParams, CursorPageResponse, @@ -71,16 +76,16 @@ interface FetchOffsetLimitPageParams extends OffsetLimitPageParams { query?: URLSearchParams; } -export function zodfetch( +export function zodfetch( schema: TResponseBodySchema, url: string, requestInit?: RequestInit, - options?: ZodFetchOptions> -): ApiPromise> { + options?: ZodFetchOptions> +): ApiPromise> { return new ApiPromise(_doZodFetch(schema, url, requestInit, options)); } -export function zodfetchCursorPage( +export function zodfetchCursorPage( schema: TItemSchema, url: string, params: FetchCursorPageParams, @@ -101,13 +106,21 @@ export function zodfetchCursorPage( query.set("page[before]", params.before); } - const cursorPageSchema = z.object({ - data: z.array(schema), - pagination: z.object({ - next: z.string().optional(), - previous: z.string().optional(), - }), - }); + const cursorPageSchema = "_zod" in schema + ? z.object({ + data: z.array(schema as any), + pagination: z.object({ + next: z.string().optional(), + previous: z.string().optional(), + }), + }) + : z3.object({ + data: z3.array(schema as any), + pagination: z3.object({ + next: z3.string().optional(), + previous: z3.string().optional(), + }), + }); const $url = new URL(url); $url.search = query.toString(); @@ -117,7 +130,7 @@ export function zodfetchCursorPage( return new CursorPagePromise(fetchResult, schema, url, params, requestInit, options); } -export function zodfetchOffsetLimitPage( +export function zodfetchOffsetLimitPage( schema: TItemSchema, url: string, params: FetchOffsetLimitPageParams, @@ -134,14 +147,23 @@ export function zodfetchOffsetLimitPage( query.set("page", String(params.page)); } - const offsetLimitPageSchema = z.object({ - data: z.array(schema), - pagination: z.object({ - currentPage: z.coerce.number(), - totalPages: z.coerce.number(), - count: z.coerce.number(), - }), - }); + const offsetLimitPageSchema = "_zod" in schema + ? z.object({ + data: z.array(schema as any), + pagination: z.object({ + currentPage: z.coerce.number(), + totalPages: z.coerce.number(), + count: z.coerce.number(), + }), + }) + : z3.object({ + data: z3.array(schema as any), + pagination: z3.object({ + currentPage: z3.coerce.number(), + totalPages: z3.coerce.number(), + count: z3.coerce.number(), + }), + }); const $url = new URL(url); $url.search = query.toString(); @@ -149,7 +171,9 @@ export function zodfetchOffsetLimitPage( const fetchResult = _doZodFetch(offsetLimitPageSchema, $url.href, requestInit, options); return new OffsetLimitPagePromise( - fetchResult as Promise>>>, + fetchResult as Promise< + ZodFetchResult>> + >, schema, url, params, @@ -195,12 +219,12 @@ async function traceZodFetch( ); } -async function _doZodFetch( +async function _doZodFetch( schema: TResponseBodySchema, url: string, requestInit?: PromiseOrValue, - options?: ZodFetchOptions> -): Promise>> { + options?: ZodFetchOptions> +): Promise>> { let $requestInit = await requestInit; return traceZodFetch({ url, requestInit: $requestInit, options }, async (span) => { @@ -223,13 +247,13 @@ async function _doZodFetch( }); } -async function _doZodFetchWithRetries( +async function _doZodFetchWithRetries( schema: TResponseBodySchema, url: string, requestInit?: RequestInit, options?: ZodFetchOptions, attempt = 1 -): Promise>> { +): Promise>> { try { const response = await context.with(suppressTracing(context.active()), () => fetch(url, requestInitWithCache(requestInit)) @@ -470,12 +494,12 @@ export class ApiPromise extends Promise { } } -export class CursorPagePromise - extends ApiPromise>> - implements AsyncIterable> +export class CursorPagePromise + extends ApiPromise>> + implements AsyncIterable> { constructor( - result: Promise>>>, + result: Promise>>>, private schema: TItemSchema, private url: string, private params: FetchCursorPageParams, @@ -490,7 +514,9 @@ export class CursorPagePromise ); } - #fetchPage(params: Omit): Promise>> { + #fetchPage( + params: Omit + ): Promise>> { return zodfetchCursorPage( this.schema, this.url, @@ -515,12 +541,12 @@ export class CursorPagePromise } } -export class OffsetLimitPagePromise - extends ApiPromise>> - implements AsyncIterable> +export class OffsetLimitPagePromise + extends ApiPromise>> + implements AsyncIterable> { constructor( - result: Promise>>>, + result: Promise>>>, private schema: TItemSchema, private url: string, private params: FetchOffsetLimitPageParams, @@ -541,7 +567,7 @@ export class OffsetLimitPagePromise #fetchPage( params: Omit - ): Promise>> { + ): Promise>> { return zodfetchOffsetLimitPage( this.schema, this.url, @@ -644,17 +670,18 @@ function injectRequestIdempotencyKey( }; } -export type ZodFetchSSEMessageValueSchema< - TDiscriminatedUnion extends z.ZodDiscriminatedUnion, -> = z.ZodFirstPartySchemaTypes | TDiscriminatedUnion; +export type ZodFetchSSEMessageValueSchema = + TSchema; export interface ZodFetchSSEMessageCatalogSchema { - [key: string]: ZodFetchSSEMessageValueSchema; + [key: string]: AnyZodSchema; } export type ZodFetchSSEMessageHandlers = Partial<{ - [K in keyof TCatalogSchema]: (payload: z.infer) => Promise | void; + [K in keyof TCatalogSchema]: ( + payload: inferZodSchemaOutput + ) => Promise | void; }>; export type ZodFetchSSEOptions = { @@ -699,6 +726,10 @@ export class ZodFetchSSEResult = statusCode?: number; }; -export async function wrapZodFetch( +export async function wrapZodFetch( schema: T, url: string, requestInit?: RequestInit, - options?: ZodFetchOptions> -): Promise>> { + options?: ZodFetchOptions> +): Promise>> { try { const response = await zodfetch(schema, url, requestInit, { retry: { diff --git a/packages/core/src/v3/apiClient/index.ts b/packages/core/src/v3/apiClient/index.ts index 759f6943971..e2ce190e3f5 100644 --- a/packages/core/src/v3/apiClient/index.ts +++ b/packages/core/src/v3/apiClient/index.ts @@ -1,5 +1,5 @@ import { nanoid } from "nanoid"; -import { z } from "zod"; +import { z } from "zod/v4"; import { VERSION } from "../../version.js"; import { isAdditionalApiKey } from "../apiKeys.js"; import type { ApiClientConfiguration } from "../apiClientManager-api.js"; diff --git a/packages/core/src/v3/apiClient/stream.ts b/packages/core/src/v3/apiClient/stream.ts index ec35f1deb63..a25f25a766b 100644 --- a/packages/core/src/v3/apiClient/stream.ts +++ b/packages/core/src/v3/apiClient/stream.ts @@ -1,4 +1,7 @@ -import type { z } from "zod"; +import type { + AnyZodSchema, + inferZodSchemaOutput, +} from "../types/schemas.js"; import { type Offset, FetchError, @@ -22,12 +25,12 @@ export type ZodShapeStreamOptions = { onError?: (e: Error) => void; }; -export type ZodShapeStreamInstance = { - stream: AsyncIterableStream>; +export type ZodShapeStreamInstance = { + stream: AsyncIterableStream>; stop: (delay?: number) => void; }; -export function zodShapeStream( +export function zodShapeStream( schema: TShapeSchema, url: string, options?: ZodShapeStreamOptions @@ -59,7 +62,7 @@ export function zodShapeStream( const readableShape = new ReadableShapeStream(shapeStream); const stream = readableShape.stream.pipeThrough( - new TransformStream>({ + new TransformStream>({ async transform(chunk, controller) { const result = schema.safeParse(chunk); @@ -73,7 +76,7 @@ export function zodShapeStream( ); return { - stream: stream as AsyncIterableStream>, + stream: stream as AsyncIterableStream>, stop: (delay?: number) => { if (delay) { setTimeout(() => { diff --git a/packages/core/src/v3/apiClient/zodCompatibility.test.ts b/packages/core/src/v3/apiClient/zodCompatibility.test.ts new file mode 100644 index 00000000000..9d091a51395 --- /dev/null +++ b/packages/core/src/v3/apiClient/zodCompatibility.test.ts @@ -0,0 +1,65 @@ +import { afterAll, beforeAll, describe, expect, it } from "vitest"; +import { createServer, type Server } from "node:http"; +import type { AddressInfo } from "node:net"; +import { z as z3 } from "zod/v3"; +import { z as z4 } from "zod/v4"; +import { zodfetchCursorPage, zodfetchOffsetLimitPage } from "./core.js"; + +describe("Zod pagination compatibility", () => { + let server: Server; + let baseUrl: string; + + beforeAll(async () => { + server = createServer((request, response) => { + response.setHeader("content-type", "application/json"); + + if (request.url?.startsWith("/cursor")) { + response.end( + JSON.stringify({ + data: [{ value: "42" }], + pagination: {}, + }) + ); + return; + } + + response.end( + JSON.stringify({ + data: [{ value: "42" }], + pagination: { currentPage: "1", totalPages: "1", count: "1" }, + }) + ); + }); + + await new Promise((resolve) => { + server.listen(0, "127.0.0.1", () => { + const address = server.address() as AddressInfo; + baseUrl = `http://127.0.0.1:${address.port}`; + resolve(); + }); + }); + }); + + afterAll(async () => { + await new Promise((resolve) => server.close(() => resolve())); + }); + + it.each([ + ["Zod 3", z3.object({ value: z3.coerce.number() })], + ["Zod 4", z4.object({ value: z4.coerce.number() })], + ])("parses cursor pages with %s caller schemas", async (_name, schema) => { + const page = await zodfetchCursorPage(schema, `${baseUrl}/cursor`, {}); + + expect(page.data).toEqual([{ value: 42 }]); + }); + + it.each([ + ["Zod 3", z3.object({ value: z3.coerce.number() })], + ["Zod 4", z4.object({ value: z4.coerce.number() })], + ])("parses offset pages with %s caller schemas", async (_name, schema) => { + const page = await zodfetchOffsetLimitPage(schema, `${baseUrl}/offset`, {}); + + expect(page.data).toEqual([{ value: 42 }]); + expect(page.pagination).toEqual({ currentPage: 1, totalPages: 1, count: 1 }); + }); +}); diff --git a/packages/core/src/v3/errors.ts b/packages/core/src/v3/errors.ts index 1fc7c5bce6c..754f1046715 100644 --- a/packages/core/src/v3/errors.ts +++ b/packages/core/src/v3/errors.ts @@ -1,4 +1,4 @@ -import type { z } from "zod"; +import type { z } from "zod/v4"; import type { DeploymentErrorData } from "./schemas/api.js"; import type { WorkerManifest } from "./schemas/build.js"; import { ImportTaskFileErrors } from "./schemas/build.js"; diff --git a/packages/core/src/v3/index.ts b/packages/core/src/v3/index.ts index 10551fca6f6..09baf386347 100644 --- a/packages/core/src/v3/index.ts +++ b/packages/core/src/v3/index.ts @@ -83,8 +83,11 @@ export * from "./config.js"; export { getSchemaParseFn, type AnySchemaParseFn, + type AnyZodSchema, type SchemaParseFn, type inferSchemaOut, + type inferZodSchemaInput, + type inferZodSchemaOutput, isSchemaZodEsque, isSchemaValibotEsque, isSchemaArkTypeEsque, diff --git a/packages/core/src/v3/runEngineWorker/supervisor/http.ts b/packages/core/src/v3/runEngineWorker/supervisor/http.ts index 450be432577..719dce89415 100644 --- a/packages/core/src/v3/runEngineWorker/supervisor/http.ts +++ b/packages/core/src/v3/runEngineWorker/supervisor/http.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; import type { WorkerApiConnectRequestBody, WorkerApiDequeueRequestBody, @@ -27,6 +27,10 @@ import { wrapZodFetch, type ApiResult, type ZodFetchOptions } from "../../zodfet import { createHeaders } from "../util.js"; import { WORKER_HEADERS } from "../consts.js"; import { SimpleStructuredLogger } from "../../utils/structuredLogger.js"; +import type { + AnyZodSchema, + inferZodSchemaOutput, +} from "../../types/schemas.js"; type SupervisorHttpClientOptions = SupervisorClientCommonOptions; @@ -61,13 +65,13 @@ export class SupervisorHttpClient { } } - private async request( + private async request( name: string, schema: T, url: string, requestInit?: RequestInit, - options?: ZodFetchOptions> - ): Promise>> { + options?: ZodFetchOptions> + ): Promise>> { const start = performance.now(); const result = await wrapZodFetch(schema, url, requestInit, options); diff --git a/packages/core/src/v3/runEngineWorker/supervisor/schemas.ts b/packages/core/src/v3/runEngineWorker/supervisor/schemas.ts index 778bdb395b7..37be07030e9 100644 --- a/packages/core/src/v3/runEngineWorker/supervisor/schemas.ts +++ b/packages/core/src/v3/runEngineWorker/supervisor/schemas.ts @@ -1,4 +1,5 @@ -import { z } from "zod"; +import { z } from "zod/v4"; +import { discriminatedUnion } from "../../utils/zod.js"; import { TaskRunExecutionResult } from "../../schemas/common.js"; import { MachineResources, @@ -28,7 +29,7 @@ export const WorkerApiHeartbeatResponseBody = z.object({ }); export type WorkerApiHeartbeatResponseBody = z.infer; -export const WorkerApiSuspendRunRequestBody = z.discriminatedUnion("success", [ +export const WorkerApiSuspendRunRequestBody = discriminatedUnion("success", [ z.object({ success: z.literal(true), checkpoint: CheckpointInput, diff --git a/packages/core/src/v3/runEngineWorker/workload/http.ts b/packages/core/src/v3/runEngineWorker/workload/http.ts index 02e5d80b911..b2581c11849 100644 --- a/packages/core/src/v3/runEngineWorker/workload/http.ts +++ b/packages/core/src/v3/runEngineWorker/workload/http.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; import type { WorkloadHeartbeatRequestBody, WorkloadRunAttemptCompleteRequestBody, diff --git a/packages/core/src/v3/runEngineWorker/workload/schemas.ts b/packages/core/src/v3/runEngineWorker/workload/schemas.ts index 14f3b8efe35..1abe9214de5 100644 --- a/packages/core/src/v3/runEngineWorker/workload/schemas.ts +++ b/packages/core/src/v3/runEngineWorker/workload/schemas.ts @@ -1,4 +1,5 @@ -import { z } from "zod"; +import { z } from "zod/v4"; +import { discriminatedUnion } from "../../utils/zod.js"; import { WorkerApiRunHeartbeatRequestBody, WorkerApiHeartbeatResponseBody, @@ -19,7 +20,7 @@ export type WorkloadHeartbeatRequestBody = z.infer; -export const WorkloadSuspendRunResponseBody = z.discriminatedUnion("ok", [ +export const WorkloadSuspendRunResponseBody = discriminatedUnion("ok", [ z.object({ ok: z.literal(true), }), diff --git a/packages/core/src/v3/schemas/api.ts b/packages/core/src/v3/schemas/api.ts index c16521270cc..9f411ce8254 100644 --- a/packages/core/src/v3/schemas/api.ts +++ b/packages/core/src/v3/schemas/api.ts @@ -1,4 +1,5 @@ -import { z } from "zod"; +import { z } from "zod/v4"; +import { discriminatedUnion } from "../utils/zod.js"; import { DeserializedJsonSchema } from "../../schemas/json.js"; import { FlushedRunMetadata, @@ -855,7 +856,7 @@ export const DeploymentFinalizedEvent = z.object({ }), }); -export const DeploymentEvent = z.discriminatedUnion("type", [ +export const DeploymentEvent = discriminatedUnion("type", [ DeploymentLogEvent, DeploymentFinalizedEvent, ]); @@ -1302,8 +1303,7 @@ const BulkActionSelectionRequestBody = { name: z.string().max(255, "Name must be less than 255 characters").optional(), }; -export const CreateBulkActionRequestBody = z - .discriminatedUnion("action", [ +export const CreateBulkActionRequestBody = discriminatedUnion("action", [ z.object({ action: z.literal("cancel"), targetRegion: z.never().optional(), @@ -1390,8 +1390,7 @@ export const ImportEnvironmentVariablesRequestBody = z.object({ override: z.boolean().optional(), // When omitted, variables default to non-secret (the DB default is false). isSecret: z.boolean().optional(), - source: z - .discriminatedUnion("type", [ + source: discriminatedUnion("type", [ z.object({ type: z.literal("user"), userId: z.string() }), z.object({ type: z.literal("integration"), integration: z.string() }), ]) diff --git a/packages/core/src/v3/schemas/build.ts b/packages/core/src/v3/schemas/build.ts index 366db9ab48f..a0a0330a09a 100644 --- a/packages/core/src/v3/schemas/build.ts +++ b/packages/core/src/v3/schemas/build.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; import { ConfigManifest } from "./config.js"; import { PromptManifest, QueueManifest, SkillManifest, TaskFile, TaskManifest } from "./schemas.js"; diff --git a/packages/core/src/v3/schemas/checkpoints.ts b/packages/core/src/v3/schemas/checkpoints.ts index f11698f2df7..1f8b032966b 100644 --- a/packages/core/src/v3/schemas/checkpoints.ts +++ b/packages/core/src/v3/schemas/checkpoints.ts @@ -1,5 +1,5 @@ import { CheckpointType, DequeuedMessage } from "./runEngine.js"; -import z from "zod"; +import z from "zod/v4"; const _CallbackUrl = z .string() diff --git a/packages/core/src/v3/schemas/common.ts b/packages/core/src/v3/schemas/common.ts index d5ec88883dc..8574dbf33bd 100644 --- a/packages/core/src/v3/schemas/common.ts +++ b/packages/core/src/v3/schemas/common.ts @@ -1,4 +1,5 @@ -import { z } from "zod"; +import { z } from "zod/v4"; +import { discriminatedUnion } from "../utils/zod.js"; import { DeserializedJsonSchema } from "../../schemas/json.js"; import type { RuntimeEnvironmentType as DBRuntimeEnvironmentType } from "@trigger.dev/database"; @@ -69,7 +70,7 @@ export const RunMetadataIncrementKeyOperation = z.object({ export type RunMetadataIncrementKeyOperation = z.infer; -export const RunMetadataChangeOperation = z.discriminatedUnion("type", [ +export const RunMetadataChangeOperation = discriminatedUnion("type", [ RunMetadataUpdateOperation, RunMetadataSetKeyOperation, RunMetadataDeleteKeyOperation, @@ -220,7 +221,7 @@ export type TaskRunInternalError = z.infer; export const TaskRunErrorCodes = TaskRunInternalError.shape.code.enum; export type TaskRunErrorCodes = TaskRunInternalError["code"]; -export const TaskRunError = z.discriminatedUnion("type", [ +export const TaskRunError = discriminatedUnion("type", [ TaskRunBuiltInError, TaskRunCustomErrorObject, TaskRunStringError, @@ -537,7 +538,7 @@ export const TaskRunSuccessfulExecutionResult = z.object({ export type TaskRunSuccessfulExecutionResult = z.infer; -export const TaskRunExecutionResult = z.discriminatedUnion("ok", [ +export const TaskRunExecutionResult = discriminatedUnion("ok", [ TaskRunSuccessfulExecutionResult, TaskRunFailedExecutionResult, ]); diff --git a/packages/core/src/v3/schemas/config.ts b/packages/core/src/v3/schemas/config.ts index f94098f2802..fa7570125f2 100644 --- a/packages/core/src/v3/schemas/config.ts +++ b/packages/core/src/v3/schemas/config.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; export const ConfigManifest = z.object({ project: z.string(), diff --git a/packages/core/src/v3/schemas/errors.ts b/packages/core/src/v3/schemas/errors.ts index 192da5b6de7..9862d25f93e 100644 --- a/packages/core/src/v3/schemas/errors.ts +++ b/packages/core/src/v3/schemas/errors.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; /** * The lifecycle state of an error group. Mirrors the dashboard's diff --git a/packages/core/src/v3/schemas/eventFilter.ts b/packages/core/src/v3/schemas/eventFilter.ts index 698ae49184f..11d3b8753dc 100644 --- a/packages/core/src/v3/schemas/eventFilter.ts +++ b/packages/core/src/v3/schemas/eventFilter.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; export const stringPatternMatchers = [ z.object({ diff --git a/packages/core/src/v3/schemas/fetch.ts b/packages/core/src/v3/schemas/fetch.ts index 88ccad4e357..edf5f581aac 100644 --- a/packages/core/src/v3/schemas/fetch.ts +++ b/packages/core/src/v3/schemas/fetch.ts @@ -1,4 +1,5 @@ -import { z } from "zod"; +import { z } from "zod/v4"; +import { discriminatedUnion } from "../utils/zod.js"; import { RetryOptions } from "./schemas.js"; import { EventFilter } from "./eventFilter.js"; import type { Prettify } from "../types/index.js"; @@ -39,7 +40,7 @@ export const FetchRetryBackoffStrategy = RetryOptions.extend({ /** The `backoff` strategy retries the request with an exponential backoff. */ export type FetchRetryBackoffStrategy = z.infer; -export const FetchRetryStrategy = z.discriminatedUnion("strategy", [ +export const FetchRetryStrategy = discriminatedUnion("strategy", [ FetchRetryHeadersStrategy, FetchRetryBackoffStrategy, ]); diff --git a/packages/core/src/v3/schemas/messages.ts b/packages/core/src/v3/schemas/messages.ts index 65784ce4a6a..7035c51a2de 100644 --- a/packages/core/src/v3/schemas/messages.ts +++ b/packages/core/src/v3/schemas/messages.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; import { ImportTaskFileErrors, WorkerManifest } from "./build.js"; import { TaskRunExecution, TaskRunExecutionResult } from "./common.js"; import { RunEngineVersionSchema, TaskRunExecutionMetrics } from "./schemas.js"; diff --git a/packages/core/src/v3/schemas/openTelemetry.ts b/packages/core/src/v3/schemas/openTelemetry.ts index 785805b94e5..2a21f9ad894 100644 --- a/packages/core/src/v3/schemas/openTelemetry.ts +++ b/packages/core/src/v3/schemas/openTelemetry.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; export const ExceptionEventProperties = z.object({ type: z.string().optional(), diff --git a/packages/core/src/v3/schemas/query.ts b/packages/core/src/v3/schemas/query.ts index f5a6d8be902..3735ddfdff2 100644 --- a/packages/core/src/v3/schemas/query.ts +++ b/packages/core/src/v3/schemas/query.ts @@ -1,4 +1,5 @@ -import { z } from "zod"; +import { z } from "zod/v4"; +import { discriminatedUnion } from "../utils/zod.js"; import type { IdempotencyKeyScope } from "../idempotency-key-catalog/catalog.js"; import type { MachinePresetName, RuntimeEnvironmentType } from "./common.js"; @@ -36,7 +37,7 @@ export const QueryExecuteCSVResponseBody = z.object({ export type QueryExecuteCSVResponseBody = z.infer; -export const QueryExecuteResponseBody = z.discriminatedUnion("format", [ +export const QueryExecuteResponseBody = discriminatedUnion("format", [ QueryExecuteJSONResponseBody, QueryExecuteCSVResponseBody, ]); diff --git a/packages/core/src/v3/schemas/queues.ts b/packages/core/src/v3/schemas/queues.ts index 34a47b34e3e..78d310174dd 100644 --- a/packages/core/src/v3/schemas/queues.ts +++ b/packages/core/src/v3/schemas/queues.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; const queueTypes = ["task", "custom"] as const; diff --git a/packages/core/src/v3/schemas/resources.ts b/packages/core/src/v3/schemas/resources.ts index 923c29355be..8dd89aa974b 100644 --- a/packages/core/src/v3/schemas/resources.ts +++ b/packages/core/src/v3/schemas/resources.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; import { QueueManifest, RetryOptions, ScheduleMetadata } from "./schemas.js"; import { MachineConfig } from "./common.js"; diff --git a/packages/core/src/v3/schemas/runEngine.ts b/packages/core/src/v3/schemas/runEngine.ts index 5d9be4b1aa8..b96d823eb67 100644 --- a/packages/core/src/v3/schemas/runEngine.ts +++ b/packages/core/src/v3/schemas/runEngine.ts @@ -1,4 +1,5 @@ -import { z } from "zod"; +import { z } from "zod/v4"; +import { discriminatedUnion } from "../utils/zod.js"; import type { Enum, RuntimeEnvironmentType } from "./common.js"; import { MachinePreset, TaskRunExecution } from "./common.js"; import { EnvironmentType } from "./schemas.js"; @@ -223,7 +224,7 @@ export const RunExecutionData = z.object({ }); export type RunExecutionData = z.infer; -export const CreateCheckpointResult = z.discriminatedUnion("ok", [ +export const CreateCheckpointResult = discriminatedUnion("ok", [ z .object({ ok: z.literal(true), diff --git a/packages/core/src/v3/schemas/schemas.ts b/packages/core/src/v3/schemas/schemas.ts index 9fd4470cc2d..8d6b6b79c01 100644 --- a/packages/core/src/v3/schemas/schemas.ts +++ b/packages/core/src/v3/schemas/schemas.ts @@ -1,4 +1,5 @@ -import { z } from "zod"; +import { z } from "zod/v4"; +import { discriminatedUnion } from "../utils/zod.js"; import type { RequireKeys } from "../types/index.js"; import { MachineConfig, @@ -95,7 +96,7 @@ export const SlidingWindowRateLimit = z.object({ ]), }); -export const RateLimitOptions = z.discriminatedUnion("type", [ +export const RateLimitOptions = discriminatedUnion("type", [ FixedWindowRateLimit, SlidingWindowRateLimit, ]); diff --git a/packages/core/src/v3/schemas/style.ts b/packages/core/src/v3/schemas/style.ts index 2f833b800ac..b34b7406fbb 100644 --- a/packages/core/src/v3/schemas/style.ts +++ b/packages/core/src/v3/schemas/style.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; export const PRIMARY_VARIANT = "primary"; export const WARM_VARIANT = "warm"; diff --git a/packages/core/src/v3/schemas/tokens.ts b/packages/core/src/v3/schemas/tokens.ts index ba01f240cdf..c1cd4914b1d 100644 --- a/packages/core/src/v3/schemas/tokens.ts +++ b/packages/core/src/v3/schemas/tokens.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; export const CreateAuthorizationCodeResponseSchema = z.object({ url: z.string().url(), diff --git a/packages/core/src/v3/schemas/warmStart.ts b/packages/core/src/v3/schemas/warmStart.ts index 1d0e43268e4..56b7a09cf20 100644 --- a/packages/core/src/v3/schemas/warmStart.ts +++ b/packages/core/src/v3/schemas/warmStart.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; export const WarmStartConnectResponse = z.object({ connectionTimeoutMs: z.number().optional(), diff --git a/packages/core/src/v3/schemas/webhooks.ts b/packages/core/src/v3/schemas/webhooks.ts index b5ed927602e..98b99f86f8a 100644 --- a/packages/core/src/v3/schemas/webhooks.ts +++ b/packages/core/src/v3/schemas/webhooks.ts @@ -1,4 +1,5 @@ -import { z } from "zod"; +import { z } from "zod/v4"; +import { discriminatedUnion } from "../utils/zod.js"; import { RunStatus } from "./api.js"; import { RuntimeEnvironmentTypeSchema, TaskRunError } from "./common.js"; @@ -257,7 +258,7 @@ const commonProperties = { }; /** Represents all possible webhook types */ -export const Webhook = z.discriminatedUnion("type", [ +export const Webhook = discriminatedUnion("type", [ /** Run failed alert webhook */ z.object({ ...commonProperties, diff --git a/packages/core/src/v3/serverOnly/httpServer.ts b/packages/core/src/v3/serverOnly/httpServer.ts index 28bf0af9ac7..5b81bf23ea2 100644 --- a/packages/core/src/v3/serverOnly/httpServer.ts +++ b/packages/core/src/v3/serverOnly/httpServer.ts @@ -1,30 +1,34 @@ import { createServer, type IncomingMessage, type ServerResponse } from "node:http"; -import { z } from "zod"; +import { z } from "zod/v4"; import { SimpleStructuredLogger } from "../utils/structuredLogger.js"; import { HttpReply, getJsonBody } from "../apps/http.js"; import type { Registry } from "prom-client"; import { Histogram, Counter } from "prom-client"; import { tryCatch } from "../../utils.js"; +import type { + AnyZodSchema, + inferZodSchemaOutput, +} from "../types/schemas.js"; const logger = new SimpleStructuredLogger("http-server"); type RouteHandler< - TParams extends z.ZodFirstPartySchemaTypes = z.ZodUnknown, - TQuery extends z.ZodFirstPartySchemaTypes = z.ZodUnknown, - TBody extends z.ZodFirstPartySchemaTypes = z.ZodUnknown, + TParams extends AnyZodSchema = z.ZodUnknown, + TQuery extends AnyZodSchema = z.ZodUnknown, + TBody extends AnyZodSchema = z.ZodUnknown, > = (ctx: { - params: z.infer; - queryParams: z.infer; - body: z.infer; + params: inferZodSchemaOutput; + queryParams: inferZodSchemaOutput; + body: inferZodSchemaOutput; req: IncomingMessage; res: ServerResponse; reply: HttpReply; }) => Promise; interface RouteDefinition< - TParams extends z.ZodFirstPartySchemaTypes = z.ZodUnknown, - TQuery extends z.ZodFirstPartySchemaTypes = z.ZodUnknown, - TBody extends z.ZodFirstPartySchemaTypes = z.ZodUnknown, + TParams extends AnyZodSchema = z.ZodUnknown, + TQuery extends AnyZodSchema = z.ZodUnknown, + TBody extends AnyZodSchema = z.ZodUnknown, > { paramsSchema?: TParams; querySchema?: TQuery; @@ -235,9 +239,9 @@ export class HttpServer { } route< - TParams extends z.ZodFirstPartySchemaTypes = z.ZodUnknown, - TQuery extends z.ZodFirstPartySchemaTypes = z.ZodUnknown, - TBody extends z.ZodFirstPartySchemaTypes = z.ZodUnknown, + TParams extends AnyZodSchema = z.ZodUnknown, + TQuery extends AnyZodSchema = z.ZodUnknown, + TBody extends AnyZodSchema = z.ZodUnknown, >(path: `/${string}`, method: HttpMethod, definition: RouteDefinition) { this.routes[path] = { ...this.routes[path], @@ -284,8 +288,8 @@ export class HttpServer { } private optionalSchema< - TSchema extends z.ZodFirstPartySchemaTypes | undefined, - TData extends TSchema extends z.ZodFirstPartySchemaTypes ? z.TypeOf : TData, + TSchema extends AnyZodSchema | undefined, + TData extends TSchema extends AnyZodSchema ? inferZodSchemaOutput : TData, >( schema: TSchema, data: TData @@ -296,14 +300,13 @@ export class HttpServer { } | { success: true; - data: TSchema extends z.ZodFirstPartySchemaTypes ? z.infer : TData; + data: TSchema extends AnyZodSchema ? inferZodSchemaOutput : TData; } { if (!schema) { return { success: true, data }; } - // zod v4 narrows the generic schema to $ZodTypes here; cast to the public ZodType (has safeParse in v3 & v4) - const parsed = (schema as unknown as z.ZodType).safeParse(data); + const parsed = schema.safeParse(data); if (!parsed.success) { return { success: false, error: parsed.error.message }; @@ -311,7 +314,7 @@ export class HttpServer { return { success: true, - data: parsed.data as TSchema extends z.ZodFirstPartySchemaTypes ? z.infer : TData, + data: parsed.data as TSchema extends AnyZodSchema ? inferZodSchemaOutput : TData, }; } diff --git a/packages/core/src/v3/sessionStreams/chatSnapshot.ts b/packages/core/src/v3/sessionStreams/chatSnapshot.ts index 84b204a134d..565a00a57ae 100644 --- a/packages/core/src/v3/sessionStreams/chatSnapshot.ts +++ b/packages/core/src/v3/sessionStreams/chatSnapshot.ts @@ -17,7 +17,7 @@ * recognise a version silently fall back to no-snapshot behaviour. */ -import { z } from "zod"; +import { z } from "zod/v4"; import type { UIMessage } from "ai"; diff --git a/packages/core/src/v3/types/schemas.test.ts b/packages/core/src/v3/types/schemas.test.ts new file mode 100644 index 00000000000..fd7160a60fe --- /dev/null +++ b/packages/core/src/v3/types/schemas.test.ts @@ -0,0 +1,39 @@ +import { describe, expect, expectTypeOf, it } from "vitest"; +import { z as z3 } from "zod/v3"; +import { z as z4 } from "zod/v4"; +import { getSchemaParseFn, isSchemaZodEsque } from "./schemas.js"; +import { + convertToolParametersToSchema, + type inferToolParameters, +} from "./tools.js"; + +describe("Zod schema compatibility", () => { + it("detects and parses Zod 3 schemas", async () => { + const schema = z3.object({ name: z3.string() }); + + expect(isSchemaZodEsque(schema)).toBe(true); + expect(convertToolParametersToSchema(schema)).toBe(schema); + await expect(getSchemaParseFn(schema)({ name: "Trigger.dev" })).resolves.toEqual({ + name: "Trigger.dev", + }); + + expectTypeOf>().toEqualTypeOf<{ name: string }>(); + }); + + it("detects and parses Zod 4 schemas", async () => { + const schema = z4.object({ name: z4.string() }); + + expect(isSchemaZodEsque(schema)).toBe(true); + expect(convertToolParametersToSchema(schema)).toBe(schema); + await expect(getSchemaParseFn(schema)({ name: "Trigger.dev" })).resolves.toEqual({ + name: "Trigger.dev", + }); + + expectTypeOf>().toEqualTypeOf<{ name: string }>(); + }); + + it("does not identify non-schemas as Zod schemas", () => { + expect(isSchemaZodEsque(null)).toBe(false); + expect(isSchemaZodEsque({})).toBe(false); + }); +}); diff --git a/packages/core/src/v3/types/schemas.ts b/packages/core/src/v3/types/schemas.ts index b4121029925..3e8529950c6 100644 --- a/packages/core/src/v3/types/schemas.ts +++ b/packages/core/src/v3/types/schemas.ts @@ -1,14 +1,24 @@ export type SchemaZodEsque = { _input: TInput; _output: TParsedInput; + parse: (input: unknown) => TParsedInput; + parseAsync: (input: unknown) => Promise; + safeParse: (input: unknown) => + | { success: true; data: TParsedInput } + | { success: false; error: any }; }; +export type AnyZodSchema = SchemaZodEsque; +export type inferZodSchemaInput = TSchema["_input"]; +export type inferZodSchemaOutput = TSchema["_output"]; + export function isSchemaZodEsque( - schema: Schema + schema: unknown ): schema is SchemaZodEsque { return ( typeof schema === "object" && - "_def" in schema && + schema !== null && + ("_zod" in schema || "_def" in schema) && "parse" in schema && "parseAsync" in schema && "safeParse" in schema diff --git a/packages/core/src/v3/types/tools.ts b/packages/core/src/v3/types/tools.ts index abf5646f9da..52fcf6f3c99 100644 --- a/packages/core/src/v3/types/tools.ts +++ b/packages/core/src/v3/types/tools.ts @@ -1,22 +1,26 @@ -import { z } from "zod"; import type { Schema as AISchema } from "ai"; -import type { Schema } from "./schemas.js"; +import { + isSchemaZodEsque, + type AnyZodSchema, + type inferZodSchemaOutput, + type Schema, +} from "./schemas.js"; -export type ToolTaskParameters = z.ZodTypeAny | AISchema; +export type ToolTaskParameters = AnyZodSchema | AISchema; export type inferToolParameters = PARAMETERS extends AISchema ? PARAMETERS["_type"] - : PARAMETERS extends z.ZodTypeAny - ? z.infer + : PARAMETERS extends AnyZodSchema + ? inferZodSchemaOutput : never; export function convertToolParametersToSchema( toolParameters: TToolParameters ): Schema { - return toolParameters instanceof z.ZodType + return isSchemaZodEsque(toolParameters) ? toolParameters - : convertAISchemaToTaskSchema(toolParameters); + : convertAISchemaToTaskSchema(toolParameters as AISchema); } function convertAISchemaToTaskSchema(schema: AISchema): Schema { diff --git a/packages/core/src/v3/utils/ioSerialization.ts b/packages/core/src/v3/utils/ioSerialization.ts index 5502afb0981..a63a4397303 100644 --- a/packages/core/src/v3/utils/ioSerialization.ts +++ b/packages/core/src/v3/utils/ioSerialization.ts @@ -1,6 +1,6 @@ import { JSONHeroPath } from "@jsonhero/path"; import type { Attributes, Span } from "@opentelemetry/api"; -import { z } from "zod"; +import { z } from "zod/v4"; import type { ApiClient } from "../apiClient/index.js"; import { apiClientManager } from "../apiClientManager-api.js"; import { diff --git a/packages/core/src/v3/utils/zod.ts b/packages/core/src/v3/utils/zod.ts new file mode 100644 index 00000000000..3019a9e4609 --- /dev/null +++ b/packages/core/src/v3/utils/zod.ts @@ -0,0 +1,16 @@ +import { z } from "zod/v4"; + +/** + * Preserve the Zod 4.0-compatible declaration shape while using newer Zod 4 + * implementations at runtime. Newer releases infer the discriminator as a + * second type parameter, which older `zod/v4` permalinks cannot consume. + */ +export type CompatibleZodDiscriminatedUnion< + Types extends readonly [z.ZodTypeAny, ...z.ZodTypeAny[]], +> = z.ZodDiscriminatedUnion; + +export function discriminatedUnion< + const Types extends readonly [z.ZodTypeAny, ...z.ZodTypeAny[]], +>(discriminator: string, options: Types): CompatibleZodDiscriminatedUnion { + return z.discriminatedUnion(discriminator, options as any) as CompatibleZodDiscriminatedUnion; +} diff --git a/packages/core/src/v3/workloadDeploymentToken.ts b/packages/core/src/v3/workloadDeploymentToken.ts index 4f0d367038f..0fd1fc3543e 100644 --- a/packages/core/src/v3/workloadDeploymentToken.ts +++ b/packages/core/src/v3/workloadDeploymentToken.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; import { generateJWT, validateJWT } from "./jwt.js"; /** diff --git a/packages/core/src/v3/zodIpc.ts b/packages/core/src/v3/zodIpc.ts index 7ee56ea654b..63ed14730de 100644 --- a/packages/core/src/v3/zodIpc.ts +++ b/packages/core/src/v3/zodIpc.ts @@ -1,10 +1,15 @@ import { randomUUID } from "crypto"; -import { z } from "zod"; +import { z } from "zod/v4"; +import { discriminatedUnion } from "./utils/zod.js"; import type { ExecutorToWorkerMessageCatalog, WorkerToExecutorMessageCatalog, } from "./schemas/messages.js"; import { ZodSchemaParsedError } from "./zodMessageHandler.js"; +import type { + inferZodSchemaInput, + inferZodSchemaOutput, +} from "./types/schemas.js"; import type { GetSocketCallbackSchema, GetSocketMessageSchema, @@ -18,13 +23,13 @@ import type { interface ZodIpcMessageSender { send>( type: K, - payload: z.input> + payload: inferZodSchemaInput> ): Promise; sendWithAck>( type: K, - payload: z.input> - ): Promise>>; + payload: inferZodSchemaInput> + ): Promise>>; } type ZodIpcMessageHandlers< @@ -32,11 +37,11 @@ type ZodIpcMessageHandlers< TEmitCatalog extends ZodSocketMessageCatalogSchema, > = Partial<{ [K in keyof TListenCatalog]: ( - payload: z.infer>, + payload: inferZodSchemaOutput>, sender: ZodIpcMessageSender ) => Promise< SocketMessageHasCallback extends true - ? z.input> + ? inferZodSchemaInput> : void >; }>; @@ -115,7 +120,7 @@ class ZodIpcMessageHandler< } } -const Packet = z.discriminatedUnion("type", [ +const Packet = discriminatedUnion("type", [ z.object({ type: z.literal("CONNECT"), sessionId: z.string().optional(), @@ -275,7 +280,7 @@ export class ZodIpcConnection< async send>( type: K, - payload: z.input> + payload: inferZodSchemaInput> ): Promise { const schema = this.opts.emitSchema[type]?.["message"]; @@ -301,9 +306,9 @@ export class ZodIpcConnection< public async sendWithAck>( type: K, - payload: z.input>, + payload: inferZodSchemaInput>, timeoutInMs?: number - ): Promise>> { + ): Promise>> { const currentId = this.#messageCounter++; return new Promise((resolve, reject) => { diff --git a/packages/core/src/v3/zodMessageHandler.ts b/packages/core/src/v3/zodMessageHandler.ts index 645e290edf3..04541abb86c 100644 --- a/packages/core/src/v3/zodMessageHandler.ts +++ b/packages/core/src/v3/zodMessageHandler.ts @@ -1,25 +1,31 @@ -import { z } from "zod"; +import type { z as z3 } from "zod/v3"; +import { z } from "zod/v4"; +import type { + AnyZodSchema, + inferZodSchemaInput, + inferZodSchemaOutput, +} from "./types/schemas.js"; import type { StructuredLogger } from "./utils/structuredLogger.js"; export class ZodSchemaParsedError extends Error { constructor( - public error: z.ZodError, + public error: z3.ZodError | z.ZodError, public payload: unknown ) { super(error.message); } } -export type ZodMessageValueSchema> = - | z.ZodFirstPartySchemaTypes - | TDiscriminatedUnion; +export type ZodMessageValueSchema = TSchema; export interface ZodMessageCatalogSchema { - [key: string]: ZodMessageValueSchema; + [key: string]: AnyZodSchema; } export type ZodMessageHandlers = Partial<{ - [K in keyof TCatalogSchema]: (payload: z.infer) => Promise; + [K in keyof TCatalogSchema]: ( + payload: inferZodSchemaOutput + ) => Promise; }>; export type ZodMessageHandlerOptions = { @@ -33,13 +39,13 @@ export type MessageFromSchema< TMessageCatalog extends ZodMessageCatalogSchema, > = { type: K; - payload: z.input; + payload: inferZodSchemaInput; }; export type MessagePayloadFromSchema< K extends keyof TMessageCatalog, TMessageCatalog extends ZodMessageCatalogSchema, -> = z.output; +> = inferZodSchemaOutput; export type MessageFromCatalog = { [K in keyof TMessageCatalog]: MessageFromSchema; @@ -233,7 +239,7 @@ export function parseMessageFromCatalog = (message: { type: keyof TMessageCatalog; - payload: z.infer; + payload: inferZodSchemaOutput; version: "v1"; }) => Promise; @@ -263,7 +269,7 @@ export class ZodMessageSender { public async send( type: K, - payload: z.input + payload: inferZodSchemaInput ) { const schema = this.#schema[type]; @@ -319,7 +325,7 @@ export class ZodMessageSender { export async function sendMessageInCatalog( catalog: TMessageCatalog, type: keyof TMessageCatalog, - payload: z.input, + payload: inferZodSchemaInput, sender: ZodMessageSenderCallback ) { const schema = catalog[type]; @@ -339,5 +345,5 @@ export async function sendMessageInCatalog = { - [K in keyof TCatalog]: (message: z.infer) => void; + [K in keyof TCatalog]: (message: inferZodSchemaOutput) => void; }; diff --git a/packages/core/src/v3/zodSocket.ts b/packages/core/src/v3/zodSocket.ts index 86fa0c1f197..a42a1d750a7 100644 --- a/packages/core/src/v3/zodSocket.ts +++ b/packages/core/src/v3/zodSocket.ts @@ -1,7 +1,11 @@ import type { ManagerOptions, Socket, SocketOptions } from "socket.io-client"; import { io } from "socket.io-client"; -import { z } from "zod"; -import { fromZodError } from "zod-validation-error"; +import { z } from "zod/v4"; +import { fromZodError } from "zod-validation-error/v4"; +import type { + inferZodSchemaInput, + inferZodSchemaOutput, +} from "./types/schemas.js"; import type { StructuredLogger } from "./utils/structuredLogger.js"; import { LogLevel, SimpleStructuredLogger } from "./utils/structuredLogger.js"; import type { EventEmitterLike, ZodMessageValueSchema } from "./zodMessageHandler.js"; @@ -20,10 +24,10 @@ export interface ZodSocketMessageCatalogSchema { export type ZodMessageCatalogToSocketIoEvents = { [K in keyof TCatalog]: SocketMessageHasCallback extends true ? ( - message: z.infer>, - callback: (ack: z.infer>) => void + message: inferZodSchemaOutput>, + callback: (ack: inferZodSchemaOutput>) => void ) => void - : (message: z.infer>) => void; + : (message: inferZodSchemaOutput>) => void; }; export type GetSocketMessageSchema< @@ -34,7 +38,7 @@ export type GetSocketMessageSchema< export type InferSocketMessageSchema< TRPCCatalog extends ZodSocketMessageCatalogSchema, TMessageType extends keyof TRPCCatalog, -> = z.infer>; +> = inferZodSchemaOutput>; export type GetSocketCallbackSchema< TRPCCatalog extends ZodSocketMessageCatalogSchema, @@ -46,7 +50,7 @@ export type GetSocketCallbackSchema< export type InferSocketCallbackSchema< TRPCCatalog extends ZodSocketMessageCatalogSchema, TMessageType extends keyof TRPCCatalog, -> = z.infer>; +> = inferZodSchemaOutput>; export type SocketMessageHasCallback< TRPCCatalog extends ZodSocketMessageCatalogSchema, @@ -56,10 +60,10 @@ export type SocketMessageHasCallback< export type ZodSocketMessageHandlers = Partial<{ [K in keyof TCatalogSchema]: ( - payload: z.infer> + payload: inferZodSchemaOutput> ) => Promise< SocketMessageHasCallback extends true - ? z.input> + ? inferZodSchemaInput> : void >; }>; @@ -77,7 +81,7 @@ type MessageFromSocketSchema< TMessageCatalog extends ZodSocketMessageCatalogSchema, > = { type: K; - payload: z.input>; + payload: inferZodSchemaInput>; }; export type MessagesFromSocketCatalog = { @@ -270,7 +274,7 @@ export class ZodSocketMessageSender>( type: K, - payload: z.input> + payload: inferZodSchemaInput> ): void { const schema = this.#schema[type]?.["message"]; @@ -295,9 +299,9 @@ export class ZodSocketMessageSender>( type: K, - payload: z.input>, + payload: inferZodSchemaInput>, timeout?: number - ): Promise>> { + ): Promise>> { const schema = this.#schema[type]?.["message"]; if (!schema) { @@ -315,7 +319,7 @@ export class ZodSocketMessageSender>; + return callbackResult as inferZodSchemaOutput>; } } diff --git a/packages/react-hooks/package.json b/packages/react-hooks/package.json index b7039450a88..cc8bc3dd300 100644 --- a/packages/react-hooks/package.json +++ b/packages/react-hooks/package.json @@ -46,13 +46,11 @@ "@types/react-dom": "*", "rimraf": "^6.0.1", "tshy": "^3.0.2", - "tsx": "4.17.0", - "zod": "4.4.3" + "tsx": "4.17.0" }, "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^18.0 || ^19.0 || ^19.0.0-rc", - "zod": "^3.25.0 || ^4.0.0" + "react-dom": "^18.0 || ^19.0 || ^19.0.0-rc" }, "engines": { "node": ">=18.20.0" diff --git a/packages/redis-worker/package.json b/packages/redis-worker/package.json index 90e1ac36bdb..ef49dfb37f5 100644 --- a/packages/redis-worker/package.json +++ b/packages/redis-worker/package.json @@ -28,6 +28,7 @@ "nanoid": "^5.0.7", "p-limit": "^6.2.0", "seedrandom": "^3.0.5", + "zod": "^3.25.76 || ^4.0.0", "cron-parser": "^4.9.0" }, "devDependencies": { @@ -38,8 +39,7 @@ "@types/seedrandom": "^3.0.8", "rimraf": "6.0.1", "tsup": "^8.4.0", - "tsx": "4.17.0", - "zod": "4.4.3" + "tsx": "4.17.0" }, "engines": { "node": ">=18.20.0" @@ -55,6 +55,6 @@ } }, "peerDependencies": { - "zod": "^3.25.0 || ^4.0.0" + "zod": "^3.25.76 || ^4.0.0" } } diff --git a/packages/redis-worker/src/fair-queue/index.ts b/packages/redis-worker/src/fair-queue/index.ts index e24de876d85..8dad207d324 100644 --- a/packages/redis-worker/src/fair-queue/index.ts +++ b/packages/redis-worker/src/fair-queue/index.ts @@ -1,9 +1,13 @@ import { createRedisClient, type Redis } from "@internal/redis"; import { SpanKind, type Span } from "@internal/tracing"; import { Logger } from "@trigger.dev/core/logger"; +import type { + AnyZodSchema, + inferZodSchemaOutput, +} from "@trigger.dev/core/v3"; import { nanoid } from "nanoid"; import { setInterval } from "node:timers/promises"; -import { type z } from "zod"; +import { type z } from "zod/v4"; import { isAbortError } from "../utils.js"; import { ConcurrencyManager } from "./concurrency.js"; import { MasterQueue } from "./masterQueue.js"; @@ -63,7 +67,7 @@ export * from "./workerQueue.js"; * * @typeParam TPayloadSchema - Zod schema for message payload validation */ -export class FairQueue { +export class FairQueue { private redis: Redis; private keys: FairQueueKeyProducer; private scheduler: FairScheduler; @@ -85,7 +89,7 @@ export class FairQueue { private visibilityTimeoutMs: number; private heartbeatIntervalMs: number; private reclaimIntervalMs: number; - private workerQueueResolver: (message: StoredMessage>) => string; + private workerQueueResolver: (message: StoredMessage>) => string; private batchClaimSize: number; // Cooloff state @@ -264,7 +268,7 @@ export class FairQueue { /** * Enqueue a single message to a queue. */ - async enqueue(options: EnqueueOptions>): Promise { + async enqueue(options: EnqueueOptions>): Promise { return this.telemetry.trace( "enqueue", async (span) => { @@ -293,7 +297,7 @@ export class FairQueue { this.queueDescriptorCache.set(options.queueId, descriptor); // Build stored message - const storedMessage: StoredMessage> = { + const storedMessage: StoredMessage> = { id: messageId, queueId: options.queueId, tenantId: options.tenantId, @@ -356,7 +360,7 @@ export class FairQueue { /** * Enqueue multiple messages to a queue. */ - async enqueueBatch(options: EnqueueBatchOptions>): Promise { + async enqueueBatch(options: EnqueueBatchOptions>): Promise { return this.telemetry.trace( "enqueueBatch", async (span) => { @@ -392,7 +396,7 @@ export class FairQueue { } } - const storedMessage: StoredMessage> = { + const storedMessage: StoredMessage> = { id: messageId, queueId: options.queueId, tenantId: options.tenantId, @@ -463,7 +467,7 @@ export class FairQueue { async getDeadLetterMessages( tenantId: string, limit: number = 100 - ): Promise>[]> { + ): Promise>[]> { if (!this.deadLetterQueueEnabled) { return []; } @@ -474,7 +478,7 @@ export class FairQueue { // Get message IDs with scores (deadLetteredAt timestamps) const results = await this.redis.zrange(dlqKey, 0, limit - 1, "WITHSCORES"); - const messages: DeadLetterMessage>[] = []; + const messages: DeadLetterMessage>[] = []; for (let i = 0; i < results.length; i += 2) { const messageId = results[i]; @@ -485,7 +489,7 @@ export class FairQueue { if (!dataJson) continue; try { - const data = JSON.parse(dataJson) as DeadLetterMessage>; + const data = JSON.parse(dataJson) as DeadLetterMessage>; data.deadLetteredAt = parseFloat(deadLetteredAtStr); messages.push(data); } catch { @@ -516,7 +520,7 @@ export class FairQueue { return false; } - const dlqMessage = JSON.parse(dataJson) as DeadLetterMessage>; + const dlqMessage = JSON.parse(dataJson) as DeadLetterMessage>; // Re-enqueue with reset attempt count await this.enqueue({ @@ -1124,7 +1128,7 @@ export class FairQueue { // Claim batch of messages with visibility timeout const claimedMessages = await this.visibilityManager.claimBatch< - StoredMessage> + StoredMessage> >(queueId, queueKey, queueItemsKey, loopId, maxClaimCount, this.visibilityTimeoutMs); if (claimedMessages.length === 0) { @@ -1194,7 +1198,7 @@ export class FairQueue { async getMessageData( messageId: string, queueId: string - ): Promise> | null> { + ): Promise> | null> { const shardId = this.masterQueue.getShardForQueue(queueId); const inflightDataKey = this.keys.inflightDataKey(shardId); const dataJson = await this.redis.hget(inflightDataKey, messageId); @@ -1204,7 +1208,7 @@ export class FairQueue { } try { - return JSON.parse(dataJson) as StoredMessage>; + return JSON.parse(dataJson) as StoredMessage>; } catch { this.logger.error("Failed to parse message data", { messageId, queueId }); return null; @@ -1236,7 +1240,7 @@ export class FairQueue { // Get stored message for concurrency release const dataJson = await this.redis.hget(inflightDataKey, messageId); - let storedMessage: StoredMessage> | null = null; + let storedMessage: StoredMessage> | null = null; if (dataJson) { try { storedMessage = JSON.parse(dataJson); @@ -1291,7 +1295,7 @@ export class FairQueue { // Get stored message for concurrency release const dataJson = await this.redis.hget(inflightDataKey, messageId); - let storedMessage: StoredMessage> | null = null; + let storedMessage: StoredMessage> | null = null; if (dataJson) { try { storedMessage = JSON.parse(dataJson); @@ -1356,7 +1360,7 @@ export class FairQueue { return; } - let storedMessage: StoredMessage>; + let storedMessage: StoredMessage>; try { storedMessage = JSON.parse(dataJson); } catch { @@ -1390,7 +1394,7 @@ export class FairQueue { // ============================================================================ async #handleMessageFailure( - storedMessage: StoredMessage>, + storedMessage: StoredMessage>, queueId: string, queueKey: string, queueItemsKey: string, @@ -1455,7 +1459,7 @@ export class FairQueue { } async #moveToDeadLetterQueue( - storedMessage: StoredMessage>, + storedMessage: StoredMessage>, errorMessage?: string ): Promise { if (!this.deadLetterQueueEnabled) { @@ -1468,7 +1472,7 @@ export class FairQueue { const dlqDataKey = this.keys.deadLetterQueueDataKey(storedMessage.tenantId); const _shardId = this.masterQueue.getShardForQueue(storedMessage.queueId); - const dlqMessage: DeadLetterMessage> = { + const dlqMessage: DeadLetterMessage> = { id: storedMessage.id, queueId: storedMessage.queueId, tenantId: storedMessage.tenantId, diff --git a/packages/redis-worker/src/fair-queue/tests/fairQueue.test.ts b/packages/redis-worker/src/fair-queue/tests/fairQueue.test.ts index 5ae1f390f9f..9e7c8c6834d 100644 --- a/packages/redis-worker/src/fair-queue/tests/fairQueue.test.ts +++ b/packages/redis-worker/src/fair-queue/tests/fairQueue.test.ts @@ -1,6 +1,6 @@ import { describe, expect, vi } from "vitest"; import { redisTest } from "@internal/testcontainers"; -import { z } from "zod"; +import { z } from "zod/v4"; import { FairQueue, DefaultFairQueueKeyProducer, diff --git a/packages/redis-worker/src/fair-queue/tests/raceConditions.test.ts b/packages/redis-worker/src/fair-queue/tests/raceConditions.test.ts index a497e2d738d..c6c8e2d5a6a 100644 --- a/packages/redis-worker/src/fair-queue/tests/raceConditions.test.ts +++ b/packages/redis-worker/src/fair-queue/tests/raceConditions.test.ts @@ -1,6 +1,6 @@ import { describe, expect, vi } from "vitest"; import { redisTest } from "@internal/testcontainers"; -import { z } from "zod"; +import { z } from "zod/v4"; import { FairQueue, DefaultFairQueueKeyProducer, diff --git a/packages/redis-worker/src/fair-queue/tests/tenantDispatch.test.ts b/packages/redis-worker/src/fair-queue/tests/tenantDispatch.test.ts index 5f02ac331f5..e03e0b31ad6 100644 --- a/packages/redis-worker/src/fair-queue/tests/tenantDispatch.test.ts +++ b/packages/redis-worker/src/fair-queue/tests/tenantDispatch.test.ts @@ -1,7 +1,7 @@ import { describe, expect } from "vitest"; import { redisTest } from "@internal/testcontainers"; import { createRedisClient } from "@internal/redis"; -import { z } from "zod"; +import { z } from "zod/v4"; import { FairQueue, DefaultFairQueueKeyProducer, diff --git a/packages/redis-worker/src/fair-queue/types.ts b/packages/redis-worker/src/fair-queue/types.ts index 3bc56b599fa..38f6461c869 100644 --- a/packages/redis-worker/src/fair-queue/types.ts +++ b/packages/redis-worker/src/fair-queue/types.ts @@ -1,7 +1,11 @@ import type { RedisOptions } from "@internal/redis"; import type { Logger } from "@trigger.dev/core/logger"; +import type { + AnyZodSchema, + inferZodSchemaOutput, +} from "@trigger.dev/core/v3"; import type { Tracer, Meter } from "@internal/tracing"; -import type { z } from "zod"; +import type { z } from "zod/v4"; import type { RetryStrategy } from "./retry.js"; // ============================================================================ @@ -371,7 +375,7 @@ export interface CooloffOptions { * * @typeParam TPayloadSchema - Zod schema for message payload validation */ -export interface FairQueueOptions { +export interface FairQueueOptions { /** Redis connection options */ redis: RedisOptions; @@ -400,7 +404,7 @@ export interface FairQueueOptions>; + workerQueue: WorkerQueueOptions>; // Retry and DLQ /** Retry and dead letter queue configuration */ diff --git a/packages/redis-worker/src/mollifier/schemas.ts b/packages/redis-worker/src/mollifier/schemas.ts index f395bad5888..4d3b77a7b11 100644 --- a/packages/redis-worker/src/mollifier/schemas.ts +++ b/packages/redis-worker/src/mollifier/schemas.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; export const BufferEntryStatus = z.enum(["QUEUED", "DRAINING", "FAILED"]); export type BufferEntryStatus = z.infer; diff --git a/packages/redis-worker/src/queue.test.ts b/packages/redis-worker/src/queue.test.ts index 8d72c2ca0b9..26b0e1ccab9 100644 --- a/packages/redis-worker/src/queue.test.ts +++ b/packages/redis-worker/src/queue.test.ts @@ -1,7 +1,7 @@ import { redisTest } from "@internal/testcontainers"; import { describe } from "node:test"; import { expect } from "vitest"; -import { z } from "zod"; +import { z } from "zod/v4"; import { SimpleQueue } from "./queue.js"; import { Logger } from "@trigger.dev/core/logger"; import { createRedisClient } from "@internal/redis"; diff --git a/packages/redis-worker/src/queue.ts b/packages/redis-worker/src/queue.ts index 63e53b334e4..5053a76b734 100644 --- a/packages/redis-worker/src/queue.ts +++ b/packages/redis-worker/src/queue.ts @@ -6,18 +6,21 @@ import { type Result, } from "@internal/redis"; import { Logger } from "@trigger.dev/core/logger"; +import type { + AnyZodSchema, + inferZodSchemaOutput, +} from "@trigger.dev/core/v3"; import { nanoid } from "nanoid"; -import type { z } from "zod"; export interface MessageCatalogSchema { - [key: string]: z.ZodFirstPartySchemaTypes | z.ZodDiscriminatedUnion; + [key: string]: AnyZodSchema; } export type MessageCatalogKey = keyof TMessageCatalog; export type MessageCatalogValue< TMessageCatalog extends MessageCatalogSchema, TKey extends MessageCatalogKey, -> = z.infer; +> = inferZodSchemaOutput; export type AnyMessageCatalog = MessageCatalogSchema; export type QueueItem = { @@ -218,7 +221,7 @@ export class SimpleQueue { } // zod v4 narrows the generic schema union here; cast to public ZodType (has safeParse in v3 & v4) - const validatedItem = (schema as z.ZodType).safeParse(parsedItem.item); + const validatedItem = schema.safeParse(parsedItem.item); if (!validatedItem.success) { this.logger.error("Invalid item in queue", { diff --git a/packages/redis-worker/src/worker.test.ts b/packages/redis-worker/src/worker.test.ts index f5659f3795b..10f373b3c79 100644 --- a/packages/redis-worker/src/worker.test.ts +++ b/packages/redis-worker/src/worker.test.ts @@ -2,7 +2,7 @@ import { isolatedRedisTest as redisTest } from "@internal/testcontainers"; import { Logger } from "@trigger.dev/core/logger"; import { describe } from "node:test"; import { expect } from "vitest"; -import { z } from "zod"; +import { z } from "zod/v4"; import { Worker } from "./worker.js"; import { createRedisClient } from "@internal/redis"; diff --git a/packages/redis-worker/src/worker.ts b/packages/redis-worker/src/worker.ts index d7e9e098eac..dc4fe092f26 100644 --- a/packages/redis-worker/src/worker.ts +++ b/packages/redis-worker/src/worker.ts @@ -12,12 +12,16 @@ import { ValueType, } from "@internal/tracing"; import { Logger } from "@trigger.dev/core/logger"; -import { calculateNextRetryDelay } from "@trigger.dev/core/v3"; +import { + calculateNextRetryDelay, + type AnyZodSchema, + type inferZodSchemaOutput, +} from "@trigger.dev/core/v3"; import { type RetryOptions } from "@trigger.dev/core/v3/schemas"; import { shutdownManager } from "@trigger.dev/core/v3/serverOnly"; import { nanoid } from "nanoid"; import pLimit from "p-limit"; -import { z } from "zod"; +import { z } from "zod/v4"; import { type AnyQueueItem, SimpleQueue } from "./queue.js"; import { parseExpression } from "cron-parser"; @@ -36,7 +40,7 @@ export type BatchConfig = { export type WorkerCatalog = { [key: string]: { - schema: z.ZodFirstPartySchemaTypes | z.ZodDiscriminatedUnion; + schema: AnyZodSchema; visibilityTimeoutMs: number; retry?: RetryOptions; cron?: string; @@ -56,7 +60,7 @@ type QueueCatalogFromWorkerCatalog = { export type JobHandlerParams = { id: string; - payload: z.infer; + payload: inferZodSchemaOutput; visibilityTimeoutMs: number; attempt: number; deduplicationKey?: string; @@ -311,7 +315,7 @@ class Worker { }: { id?: string; job: K; - payload: z.infer; + payload: inferZodSchemaOutput; visibilityTimeoutMs?: number; availableAt?: Date; cancellationKey?: string; @@ -373,7 +377,7 @@ class Worker { }: { id: string; job: K; - payload: z.infer; + payload: inferZodSchemaOutput; visibilityTimeoutMs?: number; availableAt?: Date; }) { @@ -1094,7 +1098,7 @@ class Worker { timestamp: scheduledAt.getTime(), lastTimestamp: lastTimestamp?.getTime(), cron, - } as z.infer, + } as inferZodSchemaOutput, availableAt, }); diff --git a/packages/rsc/package.json b/packages/rsc/package.json index c0232aeefae..eb6396223dc 100644 --- a/packages/rsc/package.json +++ b/packages/rsc/package.json @@ -50,11 +50,7 @@ "@types/react-dom": "*", "rimraf": "^6.0.1", "tshy": "^3.0.2", - "tsx": "4.17.0", - "zod": "4.4.3" - }, - "peerDependencies": { - "zod": "^3.25.0 || ^4.0.0" + "tsx": "4.17.0" }, "engines": { "node": ">=18.20.0" diff --git a/packages/schema-to-json/package.json b/packages/schema-to-json/package.json index cc014e30706..795dfc51651 100644 --- a/packages/schema-to-json/package.json +++ b/packages/schema-to-json/package.json @@ -44,7 +44,7 @@ "@trigger.dev/core": "workspace:*", "zod-to-json-schema": "^3.25.0", "@sodaru/yup-to-json-schema": "^2", - "zod": "4.4.3", + "zod": "^3.25.76 || ^4.0.0", "effect": "^3" }, "devDependencies": { @@ -59,6 +59,7 @@ "@arethetypeswrong/cli": "^0.18.5" }, "peerDependencies": { + "zod": "^3.25.76 || ^4.0.0", "arktype": ">=2.0.0", "runtypes": ">=5.0.0", "superstruct": ">=0.14.2", diff --git a/packages/schema-to-json/src/index.ts b/packages/schema-to-json/src/index.ts index 1cb42ef2d79..d8d428bf985 100644 --- a/packages/schema-to-json/src/index.ts +++ b/packages/schema-to-json/src/index.ts @@ -1,7 +1,7 @@ // Import JSONSchema from core to ensure compatibility import type { JSONSchema } from "@trigger.dev/core/v3"; import { zodToJsonSchema } from "zod-to-json-schema"; -import * as z4 from "zod/v4"; +import * as z4 from "zod/v4/core"; import { convertSchema } from "@sodaru/yup-to-json-schema"; import { JSONSchema as EffectJSONSchema } from "effect"; @@ -41,6 +41,13 @@ export function schemaToJsonSchema( schema: Schema, options?: ConversionOptions ): ConversionResult | undefined { + if ( + schema === null || + (typeof schema !== "object" && typeof schema !== "function") + ) { + return undefined; + } + const parser = schema as any; if (isZodSchema(parser)) { @@ -111,11 +118,18 @@ export function isZodSchema(schema: any): boolean { } function isZod3Schema(schema: any): boolean { - return "_def" in schema && "parse" in schema && "parseAsync" in schema && "safeParse" in schema; + return ( + typeof schema === "object" && + schema !== null && + "_def" in schema && + "parse" in schema && + "parseAsync" in schema && + "safeParse" in schema + ); } function isZod4Schema(schema: any): boolean { - return "_zod" in schema; + return typeof schema === "object" && schema !== null && "_zod" in schema; } function convertZodSchema(schema: any, options?: ConversionOptions): JSONSchema | undefined { diff --git a/packages/schema-to-json/tests/index.test.ts b/packages/schema-to-json/tests/index.test.ts index b266a42b3b0..a886085588f 100644 --- a/packages/schema-to-json/tests/index.test.ts +++ b/packages/schema-to-json/tests/index.test.ts @@ -265,6 +265,7 @@ describe("canConvertSchema", () => { }); it("should return false for unsupported schemas", () => { + expect(canConvertSchema(null)).toBe(false); expect(canConvertSchema({ notASchema: true })).toBe(false); expect(canConvertSchema(() => true)).toBe(false); }); diff --git a/packages/trigger-sdk/package.json b/packages/trigger-sdk/package.json index 65237e95480..07b5ae8b6d5 100644 --- a/packages/trigger-sdk/package.json +++ b/packages/trigger-sdk/package.json @@ -107,7 +107,7 @@ "@ai-sdk/otel": ">=1.0.0-0 <2", "ai": "^5.0.0 || ^6.0.0 || >=7.0.0-canary <8", "react": "^18.0 || ^19.0", - "zod": "^3.25.0 || ^4.0.0" + "zod": "^3.25.76 || ^4.0.0" }, "peerDependenciesMeta": { "@ai-sdk/otel": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ff054afc53e..71ed63149f8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1496,9 +1496,6 @@ importers: tsx: specifier: 4.17.0 version: 4.17.0 - zod: - specifier: 4.4.3 - version: 4.4.3 packages/cli-v3: dependencies: @@ -1848,9 +1845,9 @@ importers: uncrypto: specifier: ^0.1.3 version: 0.1.3 - zod-error: - specifier: 2.0.0 - version: 2.0.0(zod@4.4.3) + zod: + specifier: 4.4.3 + version: 4.4.3 zod-validation-error: specifier: ^5.0.0 version: 5.0.0(zod@4.4.3) @@ -1903,9 +1900,6 @@ importers: tsx: specifier: 4.17.0 version: 4.17.0 - zod: - specifier: 4.4.3 - version: 4.4.3 packages/plugins: dependencies: @@ -1999,9 +1993,6 @@ importers: tsx: specifier: 4.17.0 version: 4.17.0 - zod: - specifier: 4.4.3 - version: 4.4.3 packages/redis-worker: dependencies: @@ -2023,6 +2014,9 @@ importers: seedrandom: specifier: ^3.0.5 version: 3.0.5 + zod: + specifier: 4.4.3 + version: 4.4.3 devDependencies: '@internal/redis': specifier: workspace:* @@ -2048,9 +2042,6 @@ importers: tsx: specifier: 4.17.0 version: 4.17.0 - zod: - specifier: 4.4.3 - version: 4.4.3 packages/rsc: dependencies: @@ -2091,9 +2082,6 @@ importers: tsx: specifier: 4.17.0 version: 4.17.0 - zod: - specifier: 4.4.3 - version: 4.4.3 packages/schema-to-json: dependencies: From 498e3792f3165fada364152f7078a479f46833d9 Mon Sep 17 00:00:00 2001 From: Chris Arderne Date: Tue, 4 Aug 2026 14:28:19 +0100 Subject: [PATCH 19/21] fix ci --- packages/core/src/v3/apiClient/core.ts | 74 +++++++++---------- packages/core/src/v3/apiClient/stream.ts | 5 +- packages/core/src/v3/errors.ts | 5 +- packages/core/src/v3/index.ts | 2 + .../src/v3/runEngineWorker/supervisor/http.ts | 5 +- packages/core/src/v3/schemas/api.ts | 34 ++++----- packages/core/src/v3/schemas/messages.ts | 3 +- packages/core/src/v3/serverOnly/httpServer.ts | 5 +- packages/core/src/v3/types/schemas.test.ts | 5 +- packages/core/src/v3/types/schemas.ts | 16 +++- packages/core/src/v3/utils/zod.ts | 12 ++- packages/core/src/v3/zodIpc.ts | 5 +- packages/core/src/v3/zodMessageHandler.ts | 8 +- packages/core/src/v3/zodSocket.ts | 5 +- packages/redis-worker/src/fair-queue/index.ts | 21 ++++-- packages/redis-worker/src/fair-queue/types.ts | 5 +- packages/redis-worker/src/queue.ts | 5 +- packages/schema-to-json/src/index.ts | 5 +- 18 files changed, 105 insertions(+), 115 deletions(-) diff --git a/packages/core/src/v3/apiClient/core.ts b/packages/core/src/v3/apiClient/core.ts index c6f33719847..0bdd0f01f09 100644 --- a/packages/core/src/v3/apiClient/core.ts +++ b/packages/core/src/v3/apiClient/core.ts @@ -13,10 +13,7 @@ import { SemanticInternalAttributes } from "../semanticInternalAttributes.js"; import type { TriggerTracer } from "../tracer.js"; import { randomUUID } from "../utils/crypto.js"; import { accessoryAttributes } from "../utils/styleAttributes.js"; -import type { - AnyZodSchema, - inferZodSchemaOutput, -} from "../types/schemas.js"; +import type { AnyZodSchema, inferZodSchemaOutput } from "../types/schemas.js"; import type { CursorPageParams, CursorPageResponse, @@ -106,21 +103,22 @@ export function zodfetchCursorPage( query.set("page[before]", params.before); } - const cursorPageSchema = "_zod" in schema - ? z.object({ - data: z.array(schema as any), - pagination: z.object({ - next: z.string().optional(), - previous: z.string().optional(), - }), - }) - : z3.object({ - data: z3.array(schema as any), - pagination: z3.object({ - next: z3.string().optional(), - previous: z3.string().optional(), - }), - }); + const cursorPageSchema = + "_zod" in schema + ? z.object({ + data: z.array(schema as any), + pagination: z.object({ + next: z.string().optional(), + previous: z.string().optional(), + }), + }) + : z3.object({ + data: z3.array(schema as any), + pagination: z3.object({ + next: z3.string().optional(), + previous: z3.string().optional(), + }), + }); const $url = new URL(url); $url.search = query.toString(); @@ -147,23 +145,24 @@ export function zodfetchOffsetLimitPage( query.set("page", String(params.page)); } - const offsetLimitPageSchema = "_zod" in schema - ? z.object({ - data: z.array(schema as any), - pagination: z.object({ - currentPage: z.coerce.number(), - totalPages: z.coerce.number(), - count: z.coerce.number(), - }), - }) - : z3.object({ - data: z3.array(schema as any), - pagination: z3.object({ - currentPage: z3.coerce.number(), - totalPages: z3.coerce.number(), - count: z3.coerce.number(), - }), - }); + const offsetLimitPageSchema = + "_zod" in schema + ? z.object({ + data: z.array(schema as any), + pagination: z.object({ + currentPage: z.coerce.number(), + totalPages: z.coerce.number(), + count: z.coerce.number(), + }), + }) + : z3.object({ + data: z3.array(schema as any), + pagination: z3.object({ + currentPage: z3.coerce.number(), + totalPages: z3.coerce.number(), + count: z3.coerce.number(), + }), + }); const $url = new URL(url); $url.search = query.toString(); @@ -670,8 +669,7 @@ function injectRequestIdempotencyKey( }; } -export type ZodFetchSSEMessageValueSchema = - TSchema; +export type ZodFetchSSEMessageValueSchema = TSchema; export interface ZodFetchSSEMessageCatalogSchema { [key: string]: AnyZodSchema; diff --git a/packages/core/src/v3/apiClient/stream.ts b/packages/core/src/v3/apiClient/stream.ts index a25f25a766b..0124b140172 100644 --- a/packages/core/src/v3/apiClient/stream.ts +++ b/packages/core/src/v3/apiClient/stream.ts @@ -1,7 +1,4 @@ -import type { - AnyZodSchema, - inferZodSchemaOutput, -} from "../types/schemas.js"; +import type { AnyZodSchema, inferZodSchemaOutput } from "../types/schemas.js"; import { type Offset, FetchError, diff --git a/packages/core/src/v3/errors.ts b/packages/core/src/v3/errors.ts index 754f1046715..d42da7c5326 100644 --- a/packages/core/src/v3/errors.ts +++ b/packages/core/src/v3/errors.ts @@ -8,6 +8,7 @@ import { TaskMetadataFailedToParseData } from "./schemas/messages.js"; import { links } from "./links.js"; import type { ExceptionEventProperties } from "./schemas/openTelemetry.js"; import { assertExhaustive } from "../utils.js"; +import type { ZodIssueLike } from "./types/schemas.js"; /** * If you throw this, it will get converted into an INTERNAL_ERROR @@ -484,7 +485,7 @@ function correctStackTraceLine(line: string, projectDir?: string, isDev?: boolea return line.trim(); } -export function groupTaskMetadataIssuesByTask(tasks: any, issues: z.ZodIssue[]) { +export function groupTaskMetadataIssuesByTask(tasks: any, issues: ReadonlyArray) { return issues.reduce( (acc, issue) => { if (issue.path.length === 0) { @@ -553,7 +554,7 @@ export class UncaughtExceptionError extends Error { export class TaskMetadataParseError extends Error { constructor( - public readonly zodIssues: z.ZodIssue[], + public readonly zodIssues: ReadonlyArray, public readonly tasks: any ) { super(`Failed to parse task metadata`); diff --git a/packages/core/src/v3/index.ts b/packages/core/src/v3/index.ts index 09baf386347..41af37e9754 100644 --- a/packages/core/src/v3/index.ts +++ b/packages/core/src/v3/index.ts @@ -85,6 +85,8 @@ export { type AnySchemaParseFn, type AnyZodSchema, type SchemaParseFn, + type ZodErrorLike, + type ZodIssueLike, type inferSchemaOut, type inferZodSchemaInput, type inferZodSchemaOutput, diff --git a/packages/core/src/v3/runEngineWorker/supervisor/http.ts b/packages/core/src/v3/runEngineWorker/supervisor/http.ts index 719dce89415..a80edccc202 100644 --- a/packages/core/src/v3/runEngineWorker/supervisor/http.ts +++ b/packages/core/src/v3/runEngineWorker/supervisor/http.ts @@ -27,10 +27,7 @@ import { wrapZodFetch, type ApiResult, type ZodFetchOptions } from "../../zodfet import { createHeaders } from "../util.js"; import { WORKER_HEADERS } from "../consts.js"; import { SimpleStructuredLogger } from "../../utils/structuredLogger.js"; -import type { - AnyZodSchema, - inferZodSchemaOutput, -} from "../../types/schemas.js"; +import type { AnyZodSchema, inferZodSchemaOutput } from "../../types/schemas.js"; type SupervisorHttpClientOptions = SupervisorClientCommonOptions; diff --git a/packages/core/src/v3/schemas/api.ts b/packages/core/src/v3/schemas/api.ts index 9f411ce8254..31902a6b15f 100644 --- a/packages/core/src/v3/schemas/api.ts +++ b/packages/core/src/v3/schemas/api.ts @@ -1304,20 +1304,19 @@ const BulkActionSelectionRequestBody = { }; export const CreateBulkActionRequestBody = discriminatedUnion("action", [ - z.object({ - action: z.literal("cancel"), - targetRegion: z.never().optional(), - ...BulkActionSelectionRequestBody, - }), - z.object({ - action: z.literal("replay"), - targetRegion: z.string().optional(), - ...BulkActionSelectionRequestBody, - }), - ]) - .refine((body) => (body.filter ? 1 : 0) + (body.runIds ? 1 : 0) === 1, { - message: "Exactly one of filter or runIds must be provided", - }); + z.object({ + action: z.literal("cancel"), + targetRegion: z.never().optional(), + ...BulkActionSelectionRequestBody, + }), + z.object({ + action: z.literal("replay"), + targetRegion: z.string().optional(), + ...BulkActionSelectionRequestBody, + }), +]).refine((body) => (body.filter ? 1 : 0) + (body.runIds ? 1 : 0) === 1, { + message: "Exactly one of filter or runIds must be provided", +}); export type CreateBulkActionRequestBody = z.infer; @@ -1391,10 +1390,9 @@ export const ImportEnvironmentVariablesRequestBody = z.object({ // When omitted, variables default to non-secret (the DB default is false). isSecret: z.boolean().optional(), source: discriminatedUnion("type", [ - z.object({ type: z.literal("user"), userId: z.string() }), - z.object({ type: z.literal("integration"), integration: z.string() }), - ]) - .optional(), + z.object({ type: z.literal("user"), userId: z.string() }), + z.object({ type: z.literal("integration"), integration: z.string() }), + ]).optional(), }); export type ImportEnvironmentVariablesRequestBody = z.infer< diff --git a/packages/core/src/v3/schemas/messages.ts b/packages/core/src/v3/schemas/messages.ts index 7035c51a2de..d70a032b479 100644 --- a/packages/core/src/v3/schemas/messages.ts +++ b/packages/core/src/v3/schemas/messages.ts @@ -4,6 +4,7 @@ import { TaskRunExecution, TaskRunExecutionResult } from "./common.js"; import { RunEngineVersionSchema, TaskRunExecutionMetrics } from "./schemas.js"; import { CompletedWaitpoint } from "./runEngine.js"; import { DebugLogPropertiesInput } from "../runEngineWorker/supervisor/schemas.js"; +import type { ZodIssueLike } from "../types/schemas.js"; export const ServerBackgroundWorker = z.object({ id: z.string(), @@ -27,7 +28,7 @@ export const UncaughtExceptionMessage = z.object({ export const TaskMetadataFailedToParseData = z.object({ version: z.literal("v1").default("v1"), tasks: z.unknown(), - zodIssues: z.custom((v) => { + zodIssues: z.custom>((v) => { return Array.isArray(v) && v.every((issue) => typeof issue === "object" && "message" in issue); }), }); diff --git a/packages/core/src/v3/serverOnly/httpServer.ts b/packages/core/src/v3/serverOnly/httpServer.ts index 5b81bf23ea2..c6b26785068 100644 --- a/packages/core/src/v3/serverOnly/httpServer.ts +++ b/packages/core/src/v3/serverOnly/httpServer.ts @@ -5,10 +5,7 @@ import { HttpReply, getJsonBody } from "../apps/http.js"; import type { Registry } from "prom-client"; import { Histogram, Counter } from "prom-client"; import { tryCatch } from "../../utils.js"; -import type { - AnyZodSchema, - inferZodSchemaOutput, -} from "../types/schemas.js"; +import type { AnyZodSchema, inferZodSchemaOutput } from "../types/schemas.js"; const logger = new SimpleStructuredLogger("http-server"); diff --git a/packages/core/src/v3/types/schemas.test.ts b/packages/core/src/v3/types/schemas.test.ts index fd7160a60fe..764c1d89ebf 100644 --- a/packages/core/src/v3/types/schemas.test.ts +++ b/packages/core/src/v3/types/schemas.test.ts @@ -2,10 +2,7 @@ import { describe, expect, expectTypeOf, it } from "vitest"; import { z as z3 } from "zod/v3"; import { z as z4 } from "zod/v4"; import { getSchemaParseFn, isSchemaZodEsque } from "./schemas.js"; -import { - convertToolParametersToSchema, - type inferToolParameters, -} from "./tools.js"; +import { convertToolParametersToSchema, type inferToolParameters } from "./tools.js"; describe("Zod schema compatibility", () => { it("detects and parses Zod 3 schemas", async () => { diff --git a/packages/core/src/v3/types/schemas.ts b/packages/core/src/v3/types/schemas.ts index 3e8529950c6..e90ab2cd11f 100644 --- a/packages/core/src/v3/types/schemas.ts +++ b/packages/core/src/v3/types/schemas.ts @@ -3,15 +3,25 @@ export type SchemaZodEsque = { _output: TParsedInput; parse: (input: unknown) => TParsedInput; parseAsync: (input: unknown) => Promise; - safeParse: (input: unknown) => - | { success: true; data: TParsedInput } - | { success: false; error: any }; + safeParse: ( + input: unknown + ) => { success: true; data: TParsedInput } | { success: false; error: any }; }; export type AnyZodSchema = SchemaZodEsque; export type inferZodSchemaInput = TSchema["_input"]; export type inferZodSchemaOutput = TSchema["_output"]; +export type ZodIssueLike = { + message: string; + path: ReadonlyArray; +}; + +export type ZodErrorLike = { + message: string; + issues: ReadonlyArray; +}; + export function isSchemaZodEsque( schema: unknown ): schema is SchemaZodEsque { diff --git a/packages/core/src/v3/utils/zod.ts b/packages/core/src/v3/utils/zod.ts index 3019a9e4609..8f35be3e181 100644 --- a/packages/core/src/v3/utils/zod.ts +++ b/packages/core/src/v3/utils/zod.ts @@ -9,8 +9,12 @@ export type CompatibleZodDiscriminatedUnion< Types extends readonly [z.ZodTypeAny, ...z.ZodTypeAny[]], > = z.ZodDiscriminatedUnion; -export function discriminatedUnion< - const Types extends readonly [z.ZodTypeAny, ...z.ZodTypeAny[]], ->(discriminator: string, options: Types): CompatibleZodDiscriminatedUnion { - return z.discriminatedUnion(discriminator, options as any) as CompatibleZodDiscriminatedUnion; +export function discriminatedUnion( + discriminator: string, + options: Types +): CompatibleZodDiscriminatedUnion { + return z.discriminatedUnion( + discriminator, + options as any + ) as CompatibleZodDiscriminatedUnion; } diff --git a/packages/core/src/v3/zodIpc.ts b/packages/core/src/v3/zodIpc.ts index 63ed14730de..ebd5a685df7 100644 --- a/packages/core/src/v3/zodIpc.ts +++ b/packages/core/src/v3/zodIpc.ts @@ -6,10 +6,7 @@ import type { WorkerToExecutorMessageCatalog, } from "./schemas/messages.js"; import { ZodSchemaParsedError } from "./zodMessageHandler.js"; -import type { - inferZodSchemaInput, - inferZodSchemaOutput, -} from "./types/schemas.js"; +import type { inferZodSchemaInput, inferZodSchemaOutput } from "./types/schemas.js"; import type { GetSocketCallbackSchema, GetSocketMessageSchema, diff --git a/packages/core/src/v3/zodMessageHandler.ts b/packages/core/src/v3/zodMessageHandler.ts index 04541abb86c..b883e6a30b9 100644 --- a/packages/core/src/v3/zodMessageHandler.ts +++ b/packages/core/src/v3/zodMessageHandler.ts @@ -1,7 +1,7 @@ -import type { z as z3 } from "zod/v3"; import { z } from "zod/v4"; import type { AnyZodSchema, + ZodErrorLike, inferZodSchemaInput, inferZodSchemaOutput, } from "./types/schemas.js"; @@ -9,7 +9,7 @@ import type { StructuredLogger } from "./utils/structuredLogger.js"; export class ZodSchemaParsedError extends Error { constructor( - public error: z3.ZodError | z.ZodError, + public error: ZodErrorLike, public payload: unknown ) { super(error.message); @@ -23,9 +23,7 @@ export interface ZodMessageCatalogSchema { } export type ZodMessageHandlers = Partial<{ - [K in keyof TCatalogSchema]: ( - payload: inferZodSchemaOutput - ) => Promise; + [K in keyof TCatalogSchema]: (payload: inferZodSchemaOutput) => Promise; }>; export type ZodMessageHandlerOptions = { diff --git a/packages/core/src/v3/zodSocket.ts b/packages/core/src/v3/zodSocket.ts index a42a1d750a7..db9afafdaab 100644 --- a/packages/core/src/v3/zodSocket.ts +++ b/packages/core/src/v3/zodSocket.ts @@ -2,10 +2,7 @@ import type { ManagerOptions, Socket, SocketOptions } from "socket.io-client"; import { io } from "socket.io-client"; import { z } from "zod/v4"; import { fromZodError } from "zod-validation-error/v4"; -import type { - inferZodSchemaInput, - inferZodSchemaOutput, -} from "./types/schemas.js"; +import type { inferZodSchemaInput, inferZodSchemaOutput } from "./types/schemas.js"; import type { StructuredLogger } from "./utils/structuredLogger.js"; import { LogLevel, SimpleStructuredLogger } from "./utils/structuredLogger.js"; import type { EventEmitterLike, ZodMessageValueSchema } from "./zodMessageHandler.js"; diff --git a/packages/redis-worker/src/fair-queue/index.ts b/packages/redis-worker/src/fair-queue/index.ts index 8dad207d324..0cc7ec659f3 100644 --- a/packages/redis-worker/src/fair-queue/index.ts +++ b/packages/redis-worker/src/fair-queue/index.ts @@ -1,10 +1,7 @@ import { createRedisClient, type Redis } from "@internal/redis"; import { SpanKind, type Span } from "@internal/tracing"; import { Logger } from "@trigger.dev/core/logger"; -import type { - AnyZodSchema, - inferZodSchemaOutput, -} from "@trigger.dev/core/v3"; +import type { AnyZodSchema, inferZodSchemaOutput } from "@trigger.dev/core/v3"; import { nanoid } from "nanoid"; import { setInterval } from "node:timers/promises"; import { type z } from "zod/v4"; @@ -89,7 +86,9 @@ export class FairQueue { private visibilityTimeoutMs: number; private heartbeatIntervalMs: number; private reclaimIntervalMs: number; - private workerQueueResolver: (message: StoredMessage>) => string; + private workerQueueResolver: ( + message: StoredMessage> + ) => string; private batchClaimSize: number; // Cooloff state @@ -360,7 +359,9 @@ export class FairQueue { /** * Enqueue multiple messages to a queue. */ - async enqueueBatch(options: EnqueueBatchOptions>): Promise { + async enqueueBatch( + options: EnqueueBatchOptions> + ): Promise { return this.telemetry.trace( "enqueueBatch", async (span) => { @@ -489,7 +490,9 @@ export class FairQueue { if (!dataJson) continue; try { - const data = JSON.parse(dataJson) as DeadLetterMessage>; + const data = JSON.parse(dataJson) as DeadLetterMessage< + inferZodSchemaOutput + >; data.deadLetteredAt = parseFloat(deadLetteredAtStr); messages.push(data); } catch { @@ -520,7 +523,9 @@ export class FairQueue { return false; } - const dlqMessage = JSON.parse(dataJson) as DeadLetterMessage>; + const dlqMessage = JSON.parse(dataJson) as DeadLetterMessage< + inferZodSchemaOutput + >; // Re-enqueue with reset attempt count await this.enqueue({ diff --git a/packages/redis-worker/src/fair-queue/types.ts b/packages/redis-worker/src/fair-queue/types.ts index 38f6461c869..098c1116080 100644 --- a/packages/redis-worker/src/fair-queue/types.ts +++ b/packages/redis-worker/src/fair-queue/types.ts @@ -1,9 +1,6 @@ import type { RedisOptions } from "@internal/redis"; import type { Logger } from "@trigger.dev/core/logger"; -import type { - AnyZodSchema, - inferZodSchemaOutput, -} from "@trigger.dev/core/v3"; +import type { AnyZodSchema, inferZodSchemaOutput } from "@trigger.dev/core/v3"; import type { Tracer, Meter } from "@internal/tracing"; import type { z } from "zod/v4"; import type { RetryStrategy } from "./retry.js"; diff --git a/packages/redis-worker/src/queue.ts b/packages/redis-worker/src/queue.ts index 5053a76b734..0918b827f63 100644 --- a/packages/redis-worker/src/queue.ts +++ b/packages/redis-worker/src/queue.ts @@ -6,10 +6,7 @@ import { type Result, } from "@internal/redis"; import { Logger } from "@trigger.dev/core/logger"; -import type { - AnyZodSchema, - inferZodSchemaOutput, -} from "@trigger.dev/core/v3"; +import type { AnyZodSchema, inferZodSchemaOutput } from "@trigger.dev/core/v3"; import { nanoid } from "nanoid"; export interface MessageCatalogSchema { diff --git a/packages/schema-to-json/src/index.ts b/packages/schema-to-json/src/index.ts index d8d428bf985..ebaf45c7348 100644 --- a/packages/schema-to-json/src/index.ts +++ b/packages/schema-to-json/src/index.ts @@ -41,10 +41,7 @@ export function schemaToJsonSchema( schema: Schema, options?: ConversionOptions ): ConversionResult | undefined { - if ( - schema === null || - (typeof schema !== "object" && typeof schema !== "function") - ) { + if (schema === null || (typeof schema !== "object" && typeof schema !== "function")) { return undefined; } From 86d701504580067b3965129d7279bc2d99f52562 Mon Sep 17 00:00:00 2001 From: Chris Arderne Date: Tue, 4 Aug 2026 14:35:12 +0100 Subject: [PATCH 20/21] verify omitted payload --- apps/webapp/test/routesBatchGetReplicaLag.guard.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/webapp/test/routesBatchGetReplicaLag.guard.test.ts b/apps/webapp/test/routesBatchGetReplicaLag.guard.test.ts index e851dea2053..6fe7a646708 100644 --- a/apps/webapp/test/routesBatchGetReplicaLag.guard.test.ts +++ b/apps/webapp/test/routesBatchGetReplicaLag.guard.test.ts @@ -331,7 +331,7 @@ describe("routes-batch-get callers under a lagging replica", () => { holder.environment = authEnv(seed); holder.cachedRequest = { id: batchId }; - const body = JSON.stringify({ items: [{ task: "my-task", payload: null }] }); + const body = JSON.stringify({ items: [{ task: "my-task" }] }); const res = (await actionTasksBatch({ request: new Request("http://localhost/api/v2/tasks/batch", { method: "POST", From 83841b7122899fdc201475cb8016e28db51c92bc Mon Sep 17 00:00:00 2001 From: Chris Arderne Date: Tue, 4 Aug 2026 15:09:00 +0100 Subject: [PATCH 21/21] fix(core): preserve message payloads during validation --- .../core/src/v3/zodMessageHandler.test.ts | 41 +++++++++++++++++++ packages/core/src/v3/zodMessageHandler.ts | 18 ++++++-- 2 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 packages/core/src/v3/zodMessageHandler.test.ts diff --git a/packages/core/src/v3/zodMessageHandler.test.ts b/packages/core/src/v3/zodMessageHandler.test.ts new file mode 100644 index 00000000000..7f9db90e5b6 --- /dev/null +++ b/packages/core/src/v3/zodMessageHandler.test.ts @@ -0,0 +1,41 @@ +import { describe, expect, it, vi } from "vitest"; +import { z as z3 } from "zod/v3"; +import { z as z4 } from "zod/v4"; +import { ZodMessageSender, sendMessageInCatalog } from "./zodMessageHandler.js"; + +const schemas = [ + [ + "Zod 3", + z3.object({ value: z3.string().transform(Number), added: z3.string().default("default") }), + ], + [ + "Zod 4", + z4.object({ value: z4.string().transform(Number), added: z4.string().default("default") }), + ], +] as const; + +describe("Zod message sending", () => { + it.each(schemas)("ZodMessageSender preserves the original %s payload", async (_name, schema) => { + const catalog = { TEST: schema }; + const payload = { value: "42", extra: "preserved" }; + const sender = vi.fn(async (_message: unknown) => {}); + const messageSender = new ZodMessageSender({ schema: catalog, sender }); + + await messageSender.send("TEST", payload); + + expect(sender).toHaveBeenCalledWith({ type: "TEST", payload, version: "v1" }); + }); + + it.each(schemas)( + "sendMessageInCatalog preserves the original %s payload", + async (_name, schema) => { + const catalog = { TEST: schema }; + const payload = { value: "42", extra: "preserved" }; + const sender = vi.fn(async (_message: unknown) => {}); + + await sendMessageInCatalog(catalog, "TEST", payload, sender); + + expect(sender).toHaveBeenCalledWith({ type: "TEST", payload, version: "v1" }); + } + ); +}); diff --git a/packages/core/src/v3/zodMessageHandler.ts b/packages/core/src/v3/zodMessageHandler.ts index b883e6a30b9..27662d677bf 100644 --- a/packages/core/src/v3/zodMessageHandler.ts +++ b/packages/core/src/v3/zodMessageHandler.ts @@ -282,8 +282,13 @@ export class ZodMessageSender { } try { - // send the validated/normalized output so zod transforms, defaults, and coercions are applied - await this.#sender({ type, payload: parsedPayload.data, version: "v1" }); + // Preserve the original payload on the wire for backwards compatibility. Validation is + // intentionally side-effect-free here; receivers parse the payload into the schema output. + await this.#sender({ + type, + payload: payload as unknown as inferZodSchemaOutput, + version: "v1", + }); } catch (error) { console.error("[ZodMessageSender] Failed to send message", error); } @@ -338,8 +343,13 @@ export async function sendMessageInCatalog, + version: "v1", + }); } export type MessageCatalogToSocketIoEvents = {