Skip to content

Valibot plugin emits unparameterized GenericSchema, causing nested array output types to become unknown[] #4027

@yslpn

Description

@yslpn

Description

The Valibot plugin currently emits some schemas as a bare v.GenericSchema when the generated schema contains v.lazy(...) references.

Because v.GenericSchema defaults to GenericSchema<unknown, unknown>, any other schema that uses that generated schema inside v.array(...) loses the array element type. The resulting v.InferOutput<typeof schema> contains unknown[] instead of the expected referenced type array.

I can submit a pull request if the preferred fix is to avoid emitting the GenericSchema annotation and let TypeScript infer the concrete schema type.

Bug description

Given generated code like this:

// Actual
export const vChild: v.GenericSchema = v.object({
  id: v.optional(v.number()),
  next: v.optional(v.lazy(() => vChild)),
});

export const vParent = v.object({
  id: v.number(),
  children: v.optional(v.array(vChild)),
});

TypeScript infers:

v.InferOutput<typeof vParent>
// {
//   id: number;
//   children?: unknown[] | undefined;
// }

Expected:

v.InferOutput<typeof vParent>
// {
//   id: number;
//   children?: Child[] | undefined;
// }

This happens because v.GenericSchema without type parameters is equivalent to v.GenericSchema<unknown, unknown>.

A minimal Valibot-only reproduction (you can check in browser in valibot's playground):

import * as v from 'valibot';

type Child = {
  id?: number;
};

type Parent = {
  id: number;
  children?: Child[];
};

const childSchema: v.GenericSchema = v.object({
  id: v.optional(v.number()),
});

const parentSchema = v.object({
  id: v.number(),
  children: v.optional(v.array(childSchema)),
});

const parent: Parent = {
  id: 1,
  children: [],
};

const parsedParent = v.parse(parentSchema, parent);

export const result: Parent = parsedParent;

TypeScript error:

Type '{ id: number; children?: unknown[] | undefined; }' is not assignable to type 'Parent'.
  Types of property 'children' are incompatible.
    Type 'unknown[] | undefined' is not assignable to type 'Child[] | undefined'.
      Type 'unknown[]' is not assignable to type 'Child[]'.
        Type 'unknown' is not assignable to type 'Child'.

The generated schema should let TypeScript infer the concrete schema type:

// Expected
export const vChild = v.object({
  id: v.optional(v.number()),
  next: v.optional(v.lazy(() => vChild)),
});

This keeps the output type intact, so v.array(vChild) is inferred as Child[] instead of unknown[].

Reproducible example or configuration

Minimal package setup:

{
  "type": "module",
  "scripts": {
    "typecheck": "tsc --noEmit"
  },
  "dependencies": {
    "@hey-api/openapi-ts": "0.98.1",
    "valibot": "1.3.1"
  },
  "devDependencies": {
    "typescript": "5.8.3"
  }
}

Minimal Hey API config:

import { defineConfig } from '@hey-api/openapi-ts';

export default defineConfig({
  input: './openapi.yaml',
  output: './generated',
  plugins: [
    {
      enums: 'javascript',
      name: '@hey-api/typescript',
    },
    {
      name: 'valibot',
    },
  ],
});

Command:

npx openapi-ts
npx tsc --noEmit

There is also a reduced Valibot-only reproduction in:

examples/valibot-generic-schema-repro

Run it with:

npm install
npm run typecheck

OpenAPI specification (optional)

openapi: 3.1.0
info:
  title: Valibot GenericSchema Repro
  version: 1.0.0
paths:
  /parents:
    get:
      operationId: getParents
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Parent'
components:
  schemas:
    Parent:
      type: object
      required:
        - id
      properties:
        id:
          type: number
        children:
          type: array
          items:
            $ref: '#/components/schemas/Child'
    Child:
      type: object
      properties:
        id:
          type: number
        next:
          $ref: '#/components/schemas/Child'

The recursive Child.next reference makes the Valibot plugin generate a schema that needs v.lazy(...). The issue is not the lazy runtime shape itself, but the emitted bare v.GenericSchema type annotation. Removing that annotation lets TypeScript infer the concrete Valibot schema type.

System information (optional)

@hey-api/openapi-ts: 0.98.1
valibot: 1.3.1
typescript: 5.8.3
node: 24.16.0
npm: 11.13.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    bug 🔥Broken or incorrect behavior.

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions