Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: openapi-fetch 1.0 runtime hinting feature (codegen) #2204

Open
drwpow opened this issue Mar 14, 2025 · 0 comments
Open

RFC: openapi-fetch 1.0 runtime hinting feature (codegen) #2204

drwpow opened this issue Mar 14, 2025 · 0 comments
Labels
enhancement New feature or request openapi-fetch Relevant to the openapi-fetch library

Comments

@drwpow
Copy link
Contributor

drwpow commented Mar 14, 2025

Note

This is a Request for Comment, and feedback is welcome! Please read the proposal, and respond with ways in which this would help or hurt the project.

Summary

Blend the best of both worlds: zero-runtime cost performance with code generation, to enhance openapi-fetch to be “smarter” but without incurring the full weight and performance hits of a generated SDK.

Background

openapi-fetch was designed to be a thin wrapper around fetch() in runtime, with all of its type information supplied by types that get baked out of the build. In other words, a typesafe fetch() wrapper. But the problem comes in people expecting it to have information about the OpenAPI schema at runtime, which leads to confusion why it can’t simply “figure it out since that information is in my OpenAPI schema.”

Historically, this has been more of a “binary” decision tree: either build a heavyweight SDK that is often bloated, and slow, and provides little-to-no runtime benefits. Or build nothing, and let TS do the work.

But I think there’s a compromise between the two where people would be OK sacrificing maybe a couple KB of client weight, and the tiniest performance hit, to handle boilerplate necessary to tell every request exactly what to do. Even better if this is opt-in behavior that’s backwards-compatible.

Changes

This requires 2 parts: a new flag added to the openapi-typescript CLI, and a new option to openapi-fetch

CLI addition

openapi-typescript CLI already generates types for your OpenAPI schema. But adding a new flag would also generate a new, second file to be consumed with a --client-runtime flag, e.g.:

openapi-typescript my-schema.yml -o my-schema.ts --client-runtime client-runtime.ts

Note

--client-runtime is just a placeholder flag name; I expect we’ll come up with a better name for it before shipping

This will then generate a file that has some information like the following (proof-of-concept):

export default {
  // returns 204: no content
  "/no-content-path": {
    GET: {
      request: { /* request options, if any */ },
      response: { parseAs: "stream" /* other response options */ },
    },
  },
  // requires multipart/form
  "/multipart-form-upload-path": {
    POST: {
      request: { "content-type": "multipart/form-data", /* other options */ },
      response: { /* response options, if any */ },
    },
  },

  // (other, standard JSON paths are omitted)
};

This can all be used to automatically set certain params in openapi-fetch the way generated SDKs do. But in a performant way. The key difference is:

  • This is the absolute-minimum information necessary. Things don’t go in this object unless they change default behavior
  • This only contains hints NOT available from the response. If the server should respond in a certain way (content-type header, status code, etc.), then that will NOT appear in this object. This only fills in ambiguity, not redundant response information.
  • This only contains data openapi-fetch cares about. Most of your schema is “gone,” and this generated object only contains information for openapi-fetch and nothing else
  • This is opt-in. (next part)

openapi-fetch addition

From the openapi-fetch side, this would be an opt-in API like so:

  import { createClient } from 'openapi-fetch';
  import type { paths } from './my-schema';
  import runtime from './client-runtime';

  const client = createClient<paths>({
    baseUrl: 'https://my-api.com/v1/',
+   runtime,
  });

Note

Here, too, runtime is just a placeholder name. The name can be decided on later.

It would then do things like handle 204: No Content better, automatically set headers and handle responses, etc. It would solve issues like but not limited to #1883, #2069, #2195, #2017, #1933, and more.

Unknowns / problems

It’s still unclear whether or not this requires the createClient<T> generic to change or not.

  • If yes, then it’d probably make sense for the --client-runtime output to also augment the new type to provide better data, e.g.:

    import runtime, { type GeneratedPaths } from './client-runtime';
    
    const client = createClient<GeneratedPaths>({ runtime })

    However, this would be a breaking change

  • If no, then this is a cleaner upgrade, but there might be more work involved to try and sniff out how runtime overrides the default <paths> generic.

Either way it seems some additional type inference work is needed, with the tradeoff being backwards compatibility vs library complexity/possibility of conflicting types

Alternatives

openapi-fetch stays zero codegen

In other words, rejecting this proposal. We continue to have issues about requiring boilerplate parseAs, extra headers, and extra handling around openapi-fetch. It results in a simpler library overall, but retains its papercuts (and we keep repeating them until they are well-known).

Full codegen

Requiring full codegen seems like unnecessary overkill, e.g.:

import { createClient } from './my-generated-openapi-fetch';

As of now, there are no known advantages to this approach, not when we can shift the API a little and have something opt-in and backwards-compatible. This also greatly increases the chance of bugs, since it’s a completely new, untested code path where an entire runtime is generated (as opposed to a simple object, which has almost no chance of breaking).

Feedback

We’re ideally looking for comments that address:

  • Problems this would not solve (ideally link to existing issues, or file new ones!)
  • Unforeseen difficulty this would impose (e.g. “in my setup, this would 10x the code size because X, Y, Z”)
  • Alternatives not discussed
  • Bikeshed-y name changes are welcome too 🙂
@drwpow drwpow added enhancement New feature or request openapi-fetch Relevant to the openapi-fetch library labels Mar 14, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request openapi-fetch Relevant to the openapi-fetch library
Projects
Status: No status
Development

No branches or pull requests

1 participant