Skip to content

Support self referential types in zod schemas #1917

@nicholaschiang

Description

@nicholaschiang

What version of kubb is running?

4.1.2

What kind of platform do you use?

Linux

Your kubb.config.ts config file?

import { defineConfig } from '@kubb/core'
import { pluginOas } from '@kubb/plugin-oas'
import { pluginTs } from '@kubb/plugin-ts'
import { pluginZod } from '@kubb/plugin-zod'
import { pluginClient } from '@kubb/plugin-client'
import { pluginReactQuery } from '@kubb/plugin-react-query'
import { pluginMsw } from '@kubb/plugin-msw'
import { pluginFaker } from '@kubb/plugin-faker'

export default defineConfig({
  root: '.',
  input: {
    path: './openapi.json',
  },
  output: {
    path: './kubb',
    extension: { '.ts': '', '.tsx': '', '.js': '', '.json': '.json' },
  },
  plugins: [
    pluginOas(),
    pluginTs({
      enumType: 'enum',
      enumSuffix: '',
      unknownType: 'unknown',
    }),
    pluginZod({
      unknownType: 'unknown',
      dateType: 'stringOffset',
      output: { path: 'zod', banner: '// @ts-nocheck' },
    }),
    pluginClient({
      importPath: '../../client',
      paramsType: 'object',
      paramsCasing: 'camelcase',
      output: { path: 'clients', banner: '// @ts-nocheck' },
    }),
    pluginReactQuery({
      paramsType: 'object',
      paramsCasing: 'camelcase',
      client: {
        importPath: '../../client',
      },
      mutation: {
        importPath: '../../client',
      },
      output: { path: 'hooks', banner: '// @ts-nocheck' },
      suspense: false,
    }),
    pluginMsw(),
    pluginFaker({
      transformers: {
        name: (name) => `${name}Mock`,
      },
    }),
  ],
})

Swagger/OpenAPI file?

https://gist.github.com/nicholaschiang/625ca4b3c95c70de70af4e9e8c97067d

What version of external packages are you using(@tanstack-query, MSW, React, Vue, ...)

{
  "name": "@alation/alation-ai",
  "version": "11.1.3",
  "description": "",
  "repository": {
    "type": "git",
    "url": "https://github.com/alation/alation-ai.git"
  },
  "publishConfig": {
    "registry": "https://npm.pkg.github.com"
  },
  "type": "module",
  "scripts": {
    "generate": "rm -rf kubb && kubb generate",
    "typecheck": "tsc --noEmit --strict true --module esnext --moduleResolution bundler --lib es2022,dom --target es2022 --jsx react ./index.ts",
    "test": "vitest",
    "test:run": "vitest run",
    "test:coverage": "vitest run --coverage"
  },
  "files": [
    "kubb",
    "index.ts",
    "client.tsx"
  ],
  "exports": {
    ".": "./index.ts"
  },
  "author": "Nicholas Chiang <nicholas.chiang@alation.com>",
  "engines": {
    "node": ">=22"
  },
  "packageManager": "pnpm@10.13.1",
  "peerDependencies": {
    "react": "^18.3.1",
    "react-dom": "^18.3.1",
    "msw": "^2.6.8"
  },
  "devDependencies": {
    "@kubb/cli": "^4.1.2",
    "@kubb/core": "^4.1.2",
    "@kubb/plugin-client": "^4.1.2",
    "@kubb/plugin-faker": "^4.1.2",
    "@kubb/plugin-msw": "^4.1.2",
    "@kubb/plugin-oas": "^4.1.2",
    "@kubb/plugin-react-query": "^4.1.2",
    "@kubb/plugin-ts": "^4.1.2",
    "@kubb/plugin-zod": "^4.1.2",
    "@kubb/react": "^4.1.2",
    "@testing-library/react": "^16.3.0",
    "@types/react": "^19.2.0",
    "@vitest/coverage-v8": "^3.2.4",
    "jsdom": "^27.0.0",
    "typescript": "^5.9.3",
    "vitest": "^ref3.2.4"
  },
  "dependencies": {
    "@faker-js/faker": "^10.0.0",
    "@tanstack/react-query": "^5.90.2",
    "axios": "^1.12.2",
    "zod": "^4.1.12"
  }
}

What steps can reproduce the bug?

  • Run kubb generate
  • Open kubb/zod/jsonValueOutputSchema.ts
export const jsonValueOutputSchema = z.union([z.int(), z.boolean(), z.array(jsonValueOutputSchema), z.string(), z.number(), z.object({
    
    }).catchall(jsonValueOutputSchema), z.null()])
  • Notice that the constant is referred to before definition
  • Try importing that zod schema in your own code
  • See the following error:
ReferenceError: Cannot access 'jsonValueOutputSchema' before initialization
 ❯ ../../../node_modules/@alation/alation-ai/kubb/zod/jsonValueOutputSchema.ts:9:77
      7| import { z } from "zod/v4";
      8|
      9| export const jsonValueOutputSchema = z.union([z.int(), z.boolean(), z.array(jsonValueOutputSchema), z.string(), z.number(), z.object({
       |                                                                             ^
     10|
     11|     }).catchall(jsonValueOutputSchema), z.null()])
 ❯ ../../../node_modules/@alation/alation-ai/kubb/zod/genericEvaluationCaseSchema.ts:7:1

How often does this bug happen?

Always

What is the expected behaviour?

I believe these types of issues with self referential schemas can be resolved with z.lazy (ref):

const MediaResponseSchema = z.lazy(() =>
  z.object({
    mediaId: z.number(),
    childMedias: z.array(MediaResponseSchema)
  })
);

Additional information

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions