Skip to content

Commit 3148f56

Browse files
chmaltspclaude
andcommitted
refactor: remove ts-deepmerge and fix TypeScript errors properly
- Remove @ts-nocheck and fix type errors properly - Replace ts-deepmerge with simple Object.assign (deep merge not needed for flat metadata) - Add ExtendedSchemaObject type for OpenAPI 3.0 nullable compatibility - Fix ZodObject generic parameters for Zod 3/4 compatibility - Fix discriminator null check - Use ZodTypeAny instead of ZodLiteral generic for compatibility Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 0b7c653 commit 3148f56

File tree

1 file changed

+55
-33
lines changed

1 file changed

+55
-33
lines changed

packages/nextlove/src/generators/lib/zod-openapi.ts

Lines changed: 55 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
// @ts-nocheck - Vendored code with Zod 3/4 type incompatibilities (runtime works correctly)
21
/**
32
* Zod OpenAPI Generator.
43
*
@@ -28,7 +27,6 @@
2827
*/
2928

3029
import type { SchemaObject, SchemaObjectType } from "openapi3-ts/oas31"
31-
import merge from "ts-deepmerge"
3230
import { z, ZodTypeAny } from "zod"
3331
import { parseFrontMatter, testFrontMatter } from "./front-matter"
3432
import dedent from "dedent"
@@ -59,7 +57,26 @@ import {
5957
// Type alias for Zod 3/4 compatibility
6058
type AnyZodObject = z.ZodObject<any>
6159

62-
type AnatineSchemaObject = SchemaObject & { hideDefinitions?: string[] }
60+
// Extended SchemaObject that includes OpenAPI 3.0's nullable property for backwards compatibility
61+
type ExtendedSchemaObject = SchemaObject & { nullable?: boolean }
62+
type AnatineSchemaObject = ExtendedSchemaObject & { hideDefinitions?: string[] }
63+
64+
/**
65+
* Shallow merge multiple schema objects into one.
66+
* Deep merge is not needed since metaOpenApi only contains flat metadata
67+
* (description, deprecated, x-* extensions), not nested schema structures.
68+
*/
69+
function mergeSchemas(
70+
...schemas: Array<Partial<ExtendedSchemaObject> | undefined>
71+
): ExtendedSchemaObject {
72+
const result: ExtendedSchemaObject = {}
73+
for (const schema of schemas) {
74+
if (schema) {
75+
Object.assign(result, schema)
76+
}
77+
}
78+
return result
79+
}
6380

6481
export interface OpenApiZodAny extends ZodTypeAny {
6582
metaOpenApi?: AnatineSchemaObject | AnatineSchemaObject[]
@@ -162,7 +179,7 @@ function parseTransformation({
162179
}
163180
}
164181
}
165-
return merge(
182+
return mergeSchemas(
166183
{
167184
...(zodRef.description ? { description: zodRef.description } : {}),
168185
...input,
@@ -232,7 +249,7 @@ function parseString({
232249
break
233250
}
234251
})
235-
return merge(baseSchema, parseDescription(zodRef), ...schemas)
252+
return mergeSchemas(baseSchema, parseDescription(zodRef), ...schemas)
236253
}
237254

238255
function parseNumber({
@@ -266,7 +283,7 @@ function parseNumber({
266283
baseSchema.multipleOf = item.value
267284
}
268285
})
269-
return merge(baseSchema, parseDescription(zodRef), ...schemas)
286+
return mergeSchemas(baseSchema, parseDescription(zodRef), ...schemas)
270287
}
271288

272289
function getExcludedDefinitionsFromSchema(
@@ -287,9 +304,7 @@ function parseObject({
287304
schemas,
288305
useOutput,
289306
hideDefinitions,
290-
}: ParsingArgs<
291-
z.ZodObject<never, "passthrough" | "strict" | "strip">
292-
>): SchemaObject {
307+
}: ParsingArgs<AnyZodObject>): SchemaObject {
293308
let additionalProperties: SchemaObject["additionalProperties"]
294309

295310
const catchall = getCatchall(zodRef)
@@ -331,7 +346,7 @@ function parseObject({
331346
const required =
332347
requiredProperties.length > 0 ? { required: requiredProperties } : {}
333348

334-
return merge(
349+
return mergeSchemas(
335350
{
336351
type: "object" as SchemaObjectType,
337352
properties: iterateZodObject({
@@ -355,7 +370,7 @@ function parseRecord({
355370
useOutput,
356371
}: ParsingArgs<z.ZodRecord>): SchemaObject {
357372
const valueType = getRecordValueType(zodRef)
358-
return merge(
373+
return mergeSchemas(
359374
{
360375
type: "object" as SchemaObjectType,
361376
additionalProperties:
@@ -374,7 +389,7 @@ function parseBigInt({
374389
zodRef,
375390
schemas,
376391
}: ParsingArgs<z.ZodBigInt>): SchemaObject {
377-
return merge(
392+
return mergeSchemas(
378393
{ type: "integer" as SchemaObjectType, format: "int64" },
379394
parseDescription(zodRef),
380395
...schemas
@@ -385,23 +400,23 @@ function parseBoolean({
385400
zodRef,
386401
schemas,
387402
}: ParsingArgs<z.ZodBoolean>): SchemaObject {
388-
return merge(
403+
return mergeSchemas(
389404
{ type: "boolean" as SchemaObjectType },
390405
parseDescription(zodRef),
391406
...schemas
392407
)
393408
}
394409

395410
function parseDate({ zodRef, schemas }: ParsingArgs<z.ZodDate>): SchemaObject {
396-
return merge(
411+
return mergeSchemas(
397412
{ type: "string" as SchemaObjectType, format: "date-time" },
398413
parseDescription(zodRef),
399414
...schemas
400415
)
401416
}
402417

403418
function parseNull({ zodRef, schemas }: ParsingArgs<z.ZodNull>): SchemaObject {
404-
return merge(
419+
return mergeSchemas(
405420
{
406421
nullable: true,
407422
},
@@ -415,7 +430,7 @@ function parseOptional({
415430
zodRef,
416431
useOutput,
417432
}: ParsingArgs<z.ZodOptional<OpenApiZodAny>>): SchemaObject {
418-
return merge(
433+
return mergeSchemas(
419434
generateSchema(zodRef.unwrap(), useOutput),
420435
parseDescription(zodRef),
421436
...schemas
@@ -428,7 +443,7 @@ function parseNullable({
428443
useOutput,
429444
}: ParsingArgs<z.ZodNullable<OpenApiZodAny>>): SchemaObject {
430445
const schema = generateSchema(zodRef.unwrap(), useOutput)
431-
return merge(
446+
return mergeSchemas(
432447
{ ...schema, type: schema.type, nullable: true },
433448
parseDescription(zodRef),
434449
...schemas
@@ -441,7 +456,7 @@ function parseDefault({
441456
useOutput,
442457
}: ParsingArgs<z.ZodDefault<OpenApiZodAny>>): SchemaObject {
443458
const innerType = getInnerType(zodRef)
444-
return merge(
459+
return mergeSchemas(
445460
{
446461
default: getDefaultValue(zodRef),
447462
...(innerType ? generateSchema(innerType, useOutput) : {}),
@@ -467,7 +482,7 @@ function parseArray({
467482
if (minLength != null) constraints.minItems = minLength.value
468483
if (maxLength != null) constraints.maxItems = maxLength.value
469484

470-
return merge(
485+
return mergeSchemas(
471486
{
472487
type: "array" as SchemaObjectType,
473488
items: generateSchema(zodRef.element, useOutput),
@@ -480,7 +495,7 @@ function parseArray({
480495

481496
function parseLiteral({ schemas, zodRef }: ParsingArgs<any>): SchemaObject {
482497
const value = getLiteralValue(zodRef)
483-
return merge(
498+
return mergeSchemas(
484499
{
485500
type: typeof value as "string" | "number" | "boolean",
486501
enum: [value],
@@ -492,8 +507,15 @@ function parseLiteral({ schemas, zodRef }: ParsingArgs<any>): SchemaObject {
492507

493508
function parseEnum({ schemas, zodRef }: ParsingArgs<any>): SchemaObject {
494509
const values = getEnumValues(zodRef)
510+
if (!values) {
511+
return mergeSchemas(
512+
{ type: "string" },
513+
parseDescription(zodRef),
514+
...schemas
515+
)
516+
}
495517
const valuesArray = Array.isArray(values) ? values : Object.values(values)
496-
return merge(
518+
return mergeSchemas(
497519
{
498520
type: typeof valuesArray[0] as "string" | "number",
499521
enum: valuesArray,
@@ -509,7 +531,7 @@ function parseIntersection({
509531
useOutput,
510532
}: ParsingArgs<z.ZodIntersection<z.ZodTypeAny, z.ZodTypeAny>>): SchemaObject {
511533
const { left, right } = getIntersectionParts(zodRef)
512-
return merge(
534+
return mergeSchemas(
513535
{
514536
allOf: [
515537
left ? generateSchema(left, useOutput) : {},
@@ -534,14 +556,14 @@ function parseUnion({
534556
)
535557
) {
536558
// special case to transform unions of literals into enums
537-
const literals = contents as unknown as z.ZodLiteral<OpenApiZodAny>[]
559+
const literals = contents as ZodTypeAny[]
538560
const type = literals.reduce((prev, content) => {
539561
const value = getLiteralValue(content)
540562
return !prev || prev === typeof value ? typeof value : null
541563
}, null as null | string)
542564

543565
if (type) {
544-
return merge(
566+
return mergeSchemas(
545567
{
546568
type: type as "string" | "number" | "boolean",
547569
enum: literals.map((literal) => getLiteralValue(literal)),
@@ -559,7 +581,7 @@ function parseUnion({
559581
(content) => getTypeName(content) !== "ZodNull"
560582
)
561583

562-
return merge(
584+
return mergeSchemas(
563585
{
564586
oneOf: nonNullContents.map((schema) => generateSchema(schema, useOutput)),
565587
...(isNullable ? { nullable: true } : {}),
@@ -576,11 +598,11 @@ function parseDiscriminatedUnion({
576598
}: ParsingArgs<any>): SchemaObject {
577599
const discriminator = getDiscriminator(zodRef)
578600
const options = getUnionOptions(zodRef)
579-
return merge(
601+
return mergeSchemas(
580602
{
581-
discriminator: {
582-
propertyName: discriminator,
583-
},
603+
...(discriminator
604+
? { discriminator: { propertyName: discriminator } }
605+
: {}),
584606
oneOf: options.map((schema) => generateSchema(schema, useOutput)),
585607
},
586608
parseDescription(zodRef),
@@ -592,19 +614,19 @@ function parseNever({
592614
zodRef,
593615
schemas,
594616
}: ParsingArgs<z.ZodNever>): SchemaObject {
595-
return merge({ readOnly: true }, parseDescription(zodRef), ...schemas)
617+
return mergeSchemas({ readOnly: true }, parseDescription(zodRef), ...schemas)
596618
}
597619

598620
function parseBranded({ schemas, zodRef }: ParsingArgs<any>): SchemaObject {
599621
const type = getBrandedType(zodRef)
600-
return merge(type ? generateSchema(type) : {}, ...schemas)
622+
return mergeSchemas(type ? generateSchema(type) : {}, ...schemas)
601623
}
602624

603625
function catchAllParser({
604626
zodRef,
605627
schemas,
606628
}: ParsingArgs<ZodTypeAny>): SchemaObject {
607-
return merge(parseDescription(zodRef), ...schemas)
629+
return mergeSchemas(parseDescription(zodRef), ...schemas)
608630
}
609631

610632
function parsePipeline({ zodRef, useOutput }: ParsingArgs<any>): SchemaObject {
@@ -621,7 +643,7 @@ function parseReadonly({
621643
schemas,
622644
}: ParsingArgs<any>): SchemaObject {
623645
const innerType = getInnerType(zodRef)
624-
return merge(
646+
return mergeSchemas(
625647
innerType ? generateSchema(innerType, useOutput) : {},
626648
parseDescription(zodRef),
627649
...schemas

0 commit comments

Comments
 (0)