Summary
After upgrading from 0.97.1 → 0.97.2, the generated Zod file no longer typechecks for any object property that combines:
- an
$ref to a string enum schema, and
- a
default value.
This appears to be an unintended side-effect of #3884 ("use enums from TypeScript if available"). The Zod plugin now references the TypeScript-emitted enum symbol (great!), but the .default(...) emission was not updated to match — it still emits a bare string literal, which is not assignable to the TS enum type.
Versions
@hey-api/openapi-ts: 0.97.2 (also reproduces on 0.97.3)
zod: v4
typescript: 5.x
@hey-api/typescript plugin enabled (so TS enums are emitted)
What gets generated
Given an OpenAPI string enum referenced by an object property that has a default, e.g. (illustrative):
components:
schemas:
Color:
type: string
enum: [RED, GREEN, BLUE]
Paint:
type: object
properties:
primary:
allOf: [{ $ref: '#/components/schemas/Color' }]
default: RED
types.gen.ts emits a native TS enum:
export enum Color {
RED = 'RED',
GREEN = 'GREEN',
BLUE = 'BLUE',
}
zod.gen.ts (0.97.2+) emits:
import { Color } from './types.gen';
export const zColor = z.enum(Color);
export const zPaint = z.object({
primary: zColor.optional().default('RED'), // ← TS error
});
Observed error
error TS2769: No overload matches this call.
Overload 1 of 2, '(def: Color): ZodDefault<ZodOptional<ZodEnum<typeof Color>>>', gave the following error.
Argument of type '"RED"' is not assignable to parameter of type 'Color'.
Overload 2 of 2, '(def: () => Color): ZodDefault<ZodOptional<ZodEnum<typeof Color>>>', gave the following error.
Argument of type 'string' is not assignable to parameter of type '() => Color'.
Why it fails
In Zod v4, z.enum(NativeEnum) produces ZodEnum<typeof NativeEnum> whose inferred output type is the TS enum type (Color), not the string-literal union. TS string enums are nominal, so the bare literal 'RED' is not assignable to Color even though they are equal at runtime.
In 0.97.1 the generator emitted z.enum(['RED', 'GREEN', 'BLUE']), whose output is the string-literal union 'RED' | 'GREEN' | 'BLUE' and accepts .default('RED') fine. The switch to z.enum(TsEnum) (intended) was not paired with switching the default emission (unintended).
Reproduction
Any spec containing:
- a top-level string
enum schema (so a TS enum is emitted by @hey-api/typescript), and
- another schema with a property that
$refs that enum and specifies a default.
Configure both @hey-api/typescript and zod plugins, run codegen, then tsc --noEmit. The error appears on every such default. Same pattern also breaks for the kind discriminator defaults emitted by the SDK validator path.
Suggested fix
Emit defaults as enum-member references rather than string literals when the schema is rendered as z.enum(TsEnum) (and import the enum as a value, not just a type):
import { Color } from './types.gen';
primary: zColor.optional().default(Color.RED),
Alternatively, fall back to inline literal arrays (z.enum(['RED', ...])) when a default is present on a referencing property — but the member-reference fix is cleaner and preserves the new symbol-reuse behavior from #3884.
The same issue applies to the valibot plugin path that was changed in the same PR; haven't verified there but the code path looks symmetric.
Workaround
Pin to @hey-api/openapi-ts@0.97.1 until a fix is released.
Related
Summary
After upgrading from 0.97.1 → 0.97.2, the generated Zod file no longer typechecks for any object property that combines:
$refto a stringenumschema, anddefaultvalue.This appears to be an unintended side-effect of #3884 ("use enums from TypeScript if available"). The Zod plugin now references the TypeScript-emitted enum symbol (great!), but the
.default(...)emission was not updated to match — it still emits a bare string literal, which is not assignable to the TS enum type.Versions
@hey-api/openapi-ts: 0.97.2 (also reproduces on 0.97.3)zod: v4typescript: 5.x@hey-api/typescriptplugin enabled (so TS enums are emitted)What gets generated
Given an OpenAPI string enum referenced by an object property that has a
default, e.g. (illustrative):types.gen.tsemits a native TS enum:zod.gen.ts(0.97.2+) emits:Observed error
Why it fails
In Zod v4,
z.enum(NativeEnum)producesZodEnum<typeof NativeEnum>whose inferred output type is the TS enum type (Color), not the string-literal union. TS string enums are nominal, so the bare literal'RED'is not assignable toColoreven though they are equal at runtime.In 0.97.1 the generator emitted
z.enum(['RED', 'GREEN', 'BLUE']), whose output is the string-literal union'RED' | 'GREEN' | 'BLUE'and accepts.default('RED')fine. The switch toz.enum(TsEnum)(intended) was not paired with switching the default emission (unintended).Reproduction
Any spec containing:
enumschema (so a TS enum is emitted by@hey-api/typescript), and$refs that enum and specifies adefault.Configure both
@hey-api/typescriptandzodplugins, run codegen, thentsc --noEmit. The error appears on every such default. Same pattern also breaks for thekinddiscriminator defaults emitted by the SDK validator path.Suggested fix
Emit defaults as enum-member references rather than string literals when the schema is rendered as
z.enum(TsEnum)(and import the enum as a value, not just a type):Alternatively, fall back to inline literal arrays (
z.enum(['RED', ...])) when adefaultis present on a referencing property — but the member-reference fix is cleaner and preserves the new symbol-reuse behavior from #3884.The same issue applies to the
valibotplugin path that was changed in the same PR; haven't verified there but the code path looks symmetric.Workaround
Pin to
@hey-api/openapi-ts@0.97.1until a fix is released.Related
defaultfield #3727 — older, unrelated "defaults dropped on enum" issue (already fixed)