Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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;
}"
`;
12 changes: 12 additions & 0 deletions express-zod-api/tests/env.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,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