Skip to content

Allow specifying type-safe meta values#3996

Open
matthewjamesadam wants to merge 1 commit into
hey-api:mainfrom
matthewjamesadam:matt/TypesafeMetaValues
Open

Allow specifying type-safe meta values#3996
matthewjamesadam wants to merge 1 commit into
hey-api:mainfrom
matthewjamesadam:matt/TypesafeMetaValues

Conversation

@matthewjamesadam
Copy link
Copy Markdown
Contributor

@matthewjamesadam matthewjamesadam commented Jun 5, 2026

Motivation

The @hey-api/sdk plugin generates a meta field on Options hardcoded to
Record<string, unknown>, which can be used as a side-channel for data to
interceptors/middleware. However this value is in effect untyped which can lead to
bugs:

await getFoo({ meta: { retryy: false } }); // typo'd key — compiles
await getFoo({ meta: { timeout: '30s' } }); // wrong type — compiles

This issue has been previously reported here: #2425 (comment)

Solution

hey-api defines the meta property to be of type ClientMeta, which by default is Record<string, unknown>. Client applications can define this type to a concrete value via ambient declarations — the canonical
TypeScript extension-point pattern (same as vue-router's RouteMeta):

declare module '@hey-api/client-fetch' {
  interface ClientMeta {
    timeout?: number;
  }
}

Unaugmented → Meta resolves to Record<string, unknown> — identical to today's behavior, fully backward compatible.
Augmented → meta becomes strict: unknown keys, wrong types, and primitives are all rejected at the call site.

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

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

@dosubot dosubot Bot added size:M This PR changes 30-99 lines, ignoring generated files. feature 🚀 Feature request. labels Jun 5, 2026
@vercel
Copy link
Copy Markdown

vercel Bot commented Jun 5, 2026

@matthewjamesadam 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 Jun 5, 2026

🦋 Changeset detected

Latest commit: b7ee3fd

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@hey-api/openapi-ts Minor
@hey-api/custom-client Minor

Not sure what this means? Click here to learn what changesets are.

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

@codecov
Copy link
Copy Markdown

codecov Bot commented Jun 5, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 39.06%. Comparing base (174e42f) to head (b7ee3fd).
⚠️ Report is 3 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3996      +/-   ##
==========================================
- Coverage   39.07%   39.06%   -0.02%     
==========================================
  Files         610      610              
  Lines       21561    21561              
  Branches     6348     6348              
==========================================
- Hits         8426     8423       -3     
- Misses      10697    10700       +3     
  Partials     2438     2438              
Flag Coverage Δ
unittests 39.06% <ø> (-0.02%) ⬇️

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

☔ View full report in Codecov by Harness.
📢 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.

ℹ️ Minor suggestion — one inline note about the generated JSDoc.

Reviewed changes — introduces an augmentable ClientMeta interface and Meta conditional type so the SDK's meta option becomes type-safe at the call site while remaining fully backward compatible.

  • Add ClientMeta and Meta to client-core/bundle/types.ts — the single source of truth; Meta resolves to Record<string, unknown> when unaugmented and the augmented ClientMeta otherwise.
  • Add ClientMeta and Meta to custom-client/src/core/types.ts — same pattern for the standalone @hey-api/custom-client package.
  • Re-export from all 7 client bundle indexesclient-fetch, client-axios, client-ofetch, client-ky, client-nuxt, client-next, client-angular.
  • Add Meta symbol to the SDK plugin and wire it into typeOptions.ts — the meta property on the generated Options type changes from meta?: Record<string, unknown> to meta?: Meta.
  • Add type-level regression test (clientMeta.type-test.ts) — covers augmented (strict typing) and unaugmented (backward-compatible) cases.
  • Document the feature under a new "Metadata" section in the SDK plugin docs.

Pullfrog  | Fix all ➔Fix 👍s ➔View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

* field stays backward compatible. Augment it to make `meta` typesafe:
*
* ```ts
* declare module '@hey-api/client-fetch' {
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.

⚠️ The JSDoc code example hardcodes declare module '@hey-api/client-fetch', but this file is copied verbatim into every generated client output (axios, nuxt, ky, etc.). An axios user reading their core/types.gen.ts would see @hey-api/client-fetch in the example, which is misleading since they need to augment @hey-api/client-axios instead.

Technical details
# JSDoc hardcodes client-fetch module for all generated clients

## Affected sites
- `packages/openapi-ts/src/plugins/@hey-api/client-core/bundle/types.ts:98-103` — the `declare module '@hey-api/client-fetch'` JSDoc block
- All ~600 generated `core/types.gen.ts` snapshots — every client output inherits the same text

## Required outcome
- The JSDoc in the source file should not name a specific client module, or
- The `generateClientBundle`/`replaceImports` pipeline should rewrite the module name during generation

## Suggested approach
Replace the `declare module` example with a sentence like "Augment the `ClientMeta` interface on the module you import your client from." The full example lives in the docs already; the JSDoc just needs to tell the user to read the docs or look at which module they import from.

@mrlubos
Copy link
Copy Markdown
Member

mrlubos commented Jun 6, 2026

@matthewjamesadam any thoughts on inlining the Meta type instead of defining it as a separate export?

@matthewjamesadam
Copy link
Copy Markdown
Contributor Author

matthewjamesadam commented Jun 7, 2026

@matthewjamesadam any thoughts on inlining the Meta type instead of defining it as a separate export?

@mrlubos I'm definitely open to changing this -- just so I make sure I understand, are you proposing that instead of exporting Meta in the types file:

export type Meta = keyof ClientMeta extends never ? Record<string, unknown> : ClientMeta;

and using it in the sdk file:

export type Options<...> {
  ...
  meta?: Meta;
}

we instead inline it in the sdk file:

export type Options<...> {
  ...
  meta?: keyof ClientMeta extends never ? Record<string, unknown> : ClientMeta;
}

If that's what you're saying then I think that makes sense!

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

Labels

feature 🚀 Feature request. size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants