Fix classic pipe compatibility for branded outputs#5914
Fix classic pipe compatibility for branded outputs#5914colinhacks wants to merge 2 commits intomainfrom
Conversation
|
New pull request. Leaping into action...
|
|
Heads-up: this PR also resolves #5694 (and effectively #4778). The new overload's Verified locally: both #5694 reproductions fail on // packages/zod/src/v4/classic/tests/pipe-compat.test.ts (or merge into pipe.test.ts)
import { expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
test("pipe stricter source into looser target (issue #5694)", () => {
const maybeNumber = z.number().optional();
const out = z.number().pipe(maybeNumber).parse(42);
expectTypeOf(out).toEqualTypeOf<number | undefined>();
});
test("pipe transform output into nullable target (issue #5694)", () => {
const backEnd = z.object({ field: z.number().min(1).max(100).nullable() });
backEnd.extend({
field: z.string().nonempty().transform(Number).pipe(backEnd.shape.field),
});
});Either way, two more issues fall behind this PR — worth flagging in the description if you'd like. |

Fixes #5648.
This keeps the existing strict
.pipe()overload for contextual typing, then adds a Classic method fallback that checks pipe compatibility in the direction the runtime uses: the source output must be assignable to the target input. That lets branded outputs pipe into schemas that accept the corresponding unbranded input while preserving the existing contextual typing behavior for transforms.Validated with
pnpm exec tsc -p packages/zod/tsconfig.test.json --noEmitandpnpm vitest run packages/zod/src/v4/classic/tests/pipe.test.ts.