Skip to content

feat(openapi-ts): allow disabling generation of operation types#3870

Open
uoyoCsharp wants to merge 1 commit into
hey-api:mainfrom
uoyoCsharp:feat/typescript-operation-types-enabled-toggle
Open

feat(openapi-ts): allow disabling generation of operation types#3870
uoyoCsharp wants to merge 1 commit into
hey-api:mainfrom
uoyoCsharp:feat/typescript-operation-types-enabled-toggle

Conversation

@uoyoCsharp
Copy link
Copy Markdown

Description

When using @hey-api/typescript plugin, I only need the model/definition types (e.g., Pet, Order, User, Category) from my OpenAPI spec(demo: https://petstore.swagger.io/v2/swagger.json ) — not the operation-related types like AddPetData, AddPetResponse, AddPetErrors, etc.

My project uses its own HTTP client (not @hey-api/client-*), so the operation types (*Data, *Response, *Errors) are unnecessary and add noise to the generated output.

Current Behavior

The @hey-api/typescript plugin always generates all type categories:

// ✅ I want these (model/definition types)
export type Pet = { id?: number; name: string; /* ... */ };
export type Order = { id?: number; petId?: number; /* ... */ };
export type User = { id?: number; username?: string; /* ... */ };

// ❌ I don't need these (operation types)
export type AddPetData = { body: Pet; path?: never; query?: never; url: '/pet'; };
export type AddPetErrors = { 405: unknown; };
export type FindPetsByStatusData = { body?: never; path?: never; query: { status: Array<'available' | 'pending' | 'sold'> }; url: '/pet/findByStatus'; };
export type FindPetsByStatusResponses = { 200: Array<Pet>; };
export type FindPetsByStatusResponse = FindPetsByStatusResponses[keyof FindPetsByStatusResponses];
// ... dozens more operation types

The requests, responses, and errors config options only control naming patterns, not whether these types are generated at all.

Expected Behavior

Allow disabling generation of operation types via false or { enabled: false }:

// openapi-ts.config.ts
export default defineConfig({
  input: './swagger.json',
  output: 'src/types/api-generated',
  plugins: [
    {
      name: '@hey-api/typescript',
      requests: { enabled: false }, // don't generate *Data types
      responses: { enabled: false }, // don't generate *Responses / *Response types
      errors: { enabled: false }, // don't generate *Errors types
    },
  ],
});

This would produce a clean types.gen.ts containing only model definitions:

export type Pet = { id?: number; name: string; /* ... */ };
export type Order = { id?: number; petId?: number; /* ... */ };
export type User = { id?: number; username?: string; /* ... */ };
export type Category = { id?: number; name?: string; };
export type Tag = { id?: number; name?: string; };

Use Case

Many projects use @hey-api/openapi-ts only for type generation — they have their own HTTP clients (Axios wrappers, uni-app request adapters, custom fetch implementations, etc.) and only need the schema definitions for type-safe request/response typing. For example:

import type { Pet } from './api-generated/types.gen';

// Using a custom HTTP client, not @hey-api/client-*
export function getPetById(id: number) {
  return myHttpClient.get<Pet>(`/pet/${id}`);
}

In this scenario, the operation types are dead code that clutters the generated output.

Workaround

Currently I'm using selective re-export as a workaround:

// api-spec.ts - manually pick only the model types
export type { Pet, Order, User, Category, Tag } from './api-generated/types.gen';

This works but requires manual maintenance whenever the API spec adds new models.

Environment

  • @hey-api/openapi-ts: 0.97.1
  • Node.js: 22.x
  • OS: Windows

@bolt-new-by-stackblitz
Copy link
Copy Markdown

Review PR in StackBlitz Codeflow Run & review this pull request in StackBlitz Codeflow.

@vercel
Copy link
Copy Markdown

vercel Bot commented May 13, 2026

Someone is attempting to deploy a commit to the Hey API Team on Vercel.

A member of the Team first needs to authorize it.

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 13, 2026

⚠️ No Changeset found

Latest commit: bbfb52c

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. feature 🚀 Feature request. labels May 13, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented May 13, 2026

Codecov Report

❌ Patch coverage is 70.00000% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 39.58%. Comparing base (66720d9) to head (bbfb52c).

Files with missing lines Patch % Lines
...enapi-ts/src/plugins/@hey-api/typescript/config.ts 0.00% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3870      +/-   ##
==========================================
- Coverage   39.58%   39.58%   -0.01%     
==========================================
  Files         532      532              
  Lines       19581    19585       +4     
  Branches     5835     5836       +1     
==========================================
+ Hits         7751     7752       +1     
- Misses       9582     9585       +3     
  Partials     2248     2248              
Flag Coverage Δ
unittests 39.58% <70.00%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Contributor

@pullfrog pullfrog Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

Runtime behavior is correct, but this user-facing feature ships without a changeset, without docs updates, and without test coverage. Please add all three before merge.

TL;DR — Adds an enabled?: boolean toggle (with false/{ enabled: false } shorthand) to the requests, responses, and errors sub-configs of @hey-api/typescript, so users who only need model definitions can suppress the per-operation *Data/*Responses/*Response/*Errors/*Error aliases.

Key changes

  • Add FeatureToggle to three sub-configserrors, requests, and responses now intersect with FeatureToggle, matching the existing enums pattern in the same file and the Valibot/Zod precedent.
  • Boolean shorthand mappersvalueToObject gains boolean: (enabled) => ({ enabled }) for each of the three configs, so errors: false resolves to a complete config object with the naming defaults preserved.
  • Runtime guards in operationToType — emission of the data, errors/error, and responses/response symbols is gated on the corresponding enabled flag; paired derived aliases (*Error, *Response) are correctly nested inside the parent gates, so they cannot leak when their parent is disabled.

Summary | 3 files | 1 commit | base: mainfeat/typescript-operation-types-enabled-toggle


Behavior is correct, but the surface is undocumented and untested

Before: Operation types (*Data, *Errors, *Error, *Responses, *Response) were always emitted whenever the @hey-api/typescript plugin ran.
After: Each family can be suppressed via requests: false / errors: false / responses: false (or the object form), with defaults preserving today's behavior.

I traced the diff end-to-end against valueToObject's merge semantics in packages/shared/src/config/utils/config.ts and against every cross-plugin consumer of these symbols. The runtime side of the change is sound:

  • The boolean mapper preserves the other naming defaults — errors: false still produces { enabled: false, case, error: '{{name}}Error', name: '{{name}}Errors' }, so any code path that reads plugin.config.errors.error keeps working.
  • All cross-plugin consumers I checked (@hey-api/sdk, @tanstack/*, @pinia/colada, fastify, nestjs, @angular/common) use querySymbol with explicit fallbacks ('unknown', 'never', DefaultError), not referenceSymbol. Disabling the toggles widens downstream output but does not produce dangling references or broken imports.

The actionable gaps are operational, not behavioral:

  1. No changeset. .changeset/ contains only the scaffolding files. This is a user-facing feature on @hey-api/openapi-ts and the repo auto-publishes via Changesets — without an entry it will not appear in the release notes.
  2. Docs not updated. docs/openapi-ts/plugins/typescript.md currently documents only .name and .case for requests/responses and does not mention errors at all. The new enabled flag should be documented for all three, ideally with the boolean-shorthand example from the PR description.
  3. Zero test coverage for the new toggles. A repo-wide grep across packages/openapi-ts-tests/** for enabled: false / errors: false / requests: false / responses: false returns no hits, and there is no __tests__/ directory under the typescript plugin. The minimum credible coverage would be one snapshot scenario per toggle under packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/@hey-api/typescript/, exercising both the object form and the boolean shorthand so the new mapper code is hit.

config.ts · shared/operation.ts · types.ts


Note: 1 inline comment(s) dropped because they did not anchor to lines inside the PR diff:

  • packages/openapi-ts/src/plugins/@hey-api/typescript/config.ts:88 (RIGHT) — line 88 (RIGHT) is not inside a diff hunk

Pullfrog  | Fix all ➔Fix 👍s ➔View workflow run | Using Claude Opus𝕏

.export()
.type(dataResult?.type ?? $.type('never'));
plugin.node(dataNode);
if (plugin.config.requests.enabled) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: dataResult from processor.process({...}) above (the call that fills path: [...path, operation.id, 'data']) is still computed when requests.enabled === false, then discarded. The data-shape processing is the most expensive step here, so it would be cleaner to move it inside this guard alongside the symbol registration.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature 🚀 Feature request. size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant