Skip to content

[Zod Plugin] Variable used before declaration in OpenAPI schema with circular references #2672

@alirezas

Description

@alirezas

Description

The Zod plugin generates invalid TypeScript code when dealing with OpenAPI schemas that contain circular references between entities. This results in compilation errors due to variables being used before their declaration.

Bug Report

Current Behavior

When generating Zod schemas from an OpenAPI spec with circular references, the generated code contains TypeScript errors:

Block-scoped variable 'zSubregionDto' used before its declaration.

Expected Behavior

The generated Zod schemas should handle circular references properly, either by:

  1. Using z.lazy() to wrap circular references
  2. Reordering declarations to avoid forward references
  3. Providing configuration options to handle circular dependencies

Minimal Reproduction

OpenAPI Schema (simplified):

components:
  schemas:
    CountryDto:
      type: object
      properties:
        id:
          type: number
        name:
          type: string
        region:
          $ref: '#/components/schemas/RegionDto'
        subregion:
          $ref: '#/components/schemas/SubregionDto'
    
    SubregionDto:
      type: object
      properties:
        id:
          type: number
        name:
          type: string
        region:
          $ref: '#/components/schemas/RegionDto'
        countries:
          type: array
          items:
            $ref: '#/components/schemas/CountryDto'
    
    RegionDto:
      type: object
      properties:
        id:
          type: number
        name:
          type: string
        countries:
          type: array
          items:
            $ref: '#/components/schemas/CountryDto'
        subregions:
          type: array
          items:
            $ref: '#/components/schemas/SubregionDto'

Generated Code (problematic):

export const zCountryDto = z.object({
  // ... other properties
  get region() {
    return zRegionDto;  // ✅ This works with getter
  },
  subregion: zSubregionDto,  // ❌ Error: used before declaration
  // ...
});

export const zSubregionDto = z.object({
  // ... defined later
});

Configuration:

// openapi-ts.config.ts
export default defineConfig({
  input: "http://localhost:3000/api-yaml",
  output: {
    path: "src/client",
    format: "prettier",
    lint: "eslint",
    tsConfigPath: "./tsconfig.json",
  },
  plugins: [
    "@hey-api/schemas",
    { name: "@hey-api/client-axios" },
    "@tanstack/react-query",
    "zod",  // ❌ No options available for circular reference handling
    {
      dates: true,
      name: "@hey-api/transformers",
    },
    {
      enums: "javascript",
      name: "@hey-api/typescript",
    },
  ],
});

System Information

  • @hey-api/openapi-ts version: 0.84.0
  • Node.js version: v24.8.0
  • npm version: 11.6.0
  • TypeScript version: 5.9.2
  • Operating System: macOS (darwin 25.0.0)
  • Package manager: pnpm

Dependencies

{
  "devDependencies": {
    "@hey-api/openapi-ts": "0.84.0"
  },
  "dependencies": {
    "@hey-api/client-axios": "0.9.1",
    "zod": "^3.x"
  }
}

Attempted Solutions

  1. Tried configuration options (not available):

    // ❌ These don't exist in current version
    "zod": {
      circularReferenceHandler: "lazy"
    }
  2. Attempted workarounds:

    • Removing circular references from API schema (not always feasible)
    • Manual post-processing of generated files (not sustainable)

Proposed Solutions

  1. Add z.lazy() wrapper for circular references:

    export const zCountryDto = z.object({
      subregion: z.lazy(() => zSubregionDto),  // ✅ Wrapped in lazy
    });
  2. Add configuration option:

    plugins: [
      {
        name: "zod",
        circularReferenceHandler: "lazy" | "reorder" | "error"
      }
    ]
  3. Automatic dependency sorting to reorder declarations properly

Related Issues

Additional Context

This issue commonly occurs with:

  • Geographic entities (Country → Region → Country)
  • User/Organization relationships
  • Category/Subcategory hierarchies
  • Any bidirectional entity relationships

The issue prevents using the Zod plugin with realistic API schemas that model real-world entity relationships.

Reproducible example or configuration

https://stackblitz.com/edit/hey-api-client-fetch-example

OpenAPI specification (optional)

No response

System information (optional)

No response

Metadata

Metadata

Assignees

Labels

bug 🔥Something isn't workingjavascriptPull requests that update Javascript codeprioritized 🚚This issue has been prioritized and will be worked on soon

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions