Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
- The `brandHandling` should consist of postprocessing functions altering the depiction made by Zod 4;
- The `Depicter` type signature changed;
- The `optionalPropStyle` option removed from `Integration` class constructor:
- Use `.optional()` to add question mark to the object property;
- Use `.optional()` to add question mark to the object property as well as `undefined` to its type;
- Use `.or(z.undefined())` to add `undefined` to the type of the object property;
- Reasoning: https://x.com/colinhacks/status/1919292504861491252;
- `z.any()` and `z.unknown()` are not optional, details: https://v4.zod.dev/v4/changelog#changes-zunknown-optionality.
Expand Down
8 changes: 4 additions & 4 deletions example/example.client.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
type Type1 = {
title: string;
features?: Type1[];
features?: Type1[] | undefined;
};

type SomeOf<T> = T[keyof T];
Expand Down Expand Up @@ -265,15 +265,15 @@ interface PostV1AvatarRawNegativeResponseVariants {
/** get /v1/events/stream */
type GetV1EventsStreamInput = {
/** @deprecated for testing error response */
trigger?: string;
trigger?: string | undefined;
};

/** get /v1/events/stream */
type GetV1EventsStreamPositiveVariant1 = {
data: number;
event: "time";
id?: string;
retry?: number;
id?: string | undefined;
retry?: number | undefined;
};

/** get /v1/events/stream */
Expand Down
8 changes: 7 additions & 1 deletion express-zod-api/src/typescript-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,17 @@ export const makeInterfaceProp = (
comment,
}: { isOptional?: boolean; isDeprecated?: boolean; comment?: string } = {},
) => {
const propType = ensureTypeNode(value);
const node = f.createPropertySignature(
undefined,
makePropertyIdentifier(name),
isOptional ? f.createToken(ts.SyntaxKind.QuestionToken) : undefined,
ensureTypeNode(value),
isOptional
? f.createUnionTypeNode([
propType,
ensureTypeNode(ts.SyntaxKind.UndefinedKeyword),
])
: propType,
);
const jsdoc = R.reject(R.isNil, [
isDeprecated ? "@deprecated" : undefined,
Expand Down
4 changes: 2 additions & 2 deletions express-zod-api/tests/__snapshots__/integration.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -282,14 +282,14 @@ exports[`Integration > Should treat optionals the same way as z.infer() by defau

/** post /v1/test-with-dashes */
type PostV1TestWithDashesInput = {
opt?: string;
opt?: string | undefined;
};

/** post /v1/test-with-dashes */
type PostV1TestWithDashesPositiveVariant1 = {
status: "success";
data: {
similar?: number;
similar?: number | undefined;
};
};

Expand Down
22 changes: 11 additions & 11 deletions express-zod-api/tests/__snapshots__/zts.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ exports[`zod-to-ts > Example > should produce the expected results 1`] = `
any: any;
unknown: any;
never: any;
optionalString?: string;
optionalString?: string | undefined;
nullablePartialObject: {
string?: string;
number?: number;
fixedArrayOfString?: string[];
string?: string | undefined;
number?: number | undefined;
fixedArrayOfString?: string[] | undefined;
object?: {
string: string;
};
} | undefined;
} | null;
tuple: [
string,
Expand Down Expand Up @@ -57,7 +57,7 @@ exports[`zod-to-ts > Example > should produce the expected results 1`] = `
set: any;
intersection: (string & number) | bigint;
promise: any;
optDefaultString?: string;
optDefaultString?: string | undefined;
refinedStringWithSomeBullshit: (string | number) & (bigint | null);
nativeEnum: "A" | "apple" | "banana" | "cantaloupe" | 5;
lazy: SomeType;
Expand Down Expand Up @@ -127,15 +127,15 @@ exports[`zod-to-ts > Issue #2352: intersection of objects having same prop %# >
"{
query: string;
} & {
query?: string;
query?: string | undefined;
}"
`;

exports[`zod-to-ts > Issue #2352: intersection of objects having same prop %# > should not flatten the result for objects with a conflicting prop 3 1`] = `
"{
query: string;
} & {
query?: string;
query?: string | undefined;
}"
`;

Expand Down Expand Up @@ -272,17 +272,17 @@ exports[`zod-to-ts > z.optional() > Zod 4: does not add undefined to it, unwrap

exports[`zod-to-ts > z.optional() > Zod 4: should add question mark only to optional props 1`] = `
"{
optional?: string;
optional?: string | undefined;
required: string;
transform: number;
or: number | string;
tuple?: [
string,
number,
{
optional?: string;
optional?: string | undefined;
required: string;
}
];
] | undefined;
}"
`;
13 changes: 13 additions & 0 deletions express-zod-api/tests/env.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import createHttpError from "http-errors";
import * as R from "ramda";
import { expectTypeOf } from "vitest";
import { z } from "zod";

describe("Environment checks", () => {
Expand Down Expand Up @@ -122,6 +123,18 @@ describe("Environment checks", () => {
expect(schema._zod.def.shape.three._zod.optionality).toBe("defaulted");
/** @link https://github.com/colinhacks/zod/issues/4322 */
expect(schema._zod.def.shape.four._zod.optionality).not.toBe("optional"); // <— undefined
expectTypeOf<z.input<typeof schema>>().toEqualTypeOf<{
one: boolean;
two?: boolean | undefined;
three?: boolean | undefined;
four: boolean | undefined;
}>();
expectTypeOf<z.output<typeof schema>>().toEqualTypeOf<{
one: boolean;
two?: boolean | undefined;
three: boolean;
four: boolean;
}>();
});

test("coerce is safe for nullable and optional", () => {
Expand Down