openapi-ts icon indicating copy to clipboard operation
openapi-ts copied to clipboard

Legacy to standalone client migration

Open tervay opened this issue 1 year ago • 7 comments

Description

First I want to say thanks to the developers of this project, I appreciate the direction the project is going.

We have a large API that is based entirely on path parameters, e.g. /pets/{petId}. A sample OpenAPI client can be found here, or here:

openapi: 3.0.0
info:
  title: Widget Service
  version: 0.0.0
tags: []
paths:
  /pets/{petId}:
    get:
      operationId: Pets_read
      parameters:
        - name: petId
          in: path
          required: true
          schema:
            type: integer
            format: int32
      responses:
        '200':
          description: The request has succeeded.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Pet'
components:
  schemas:
    Pet:
      type: object
      required:
        - petId
      properties:
        petId:
          type: integer
          format: int32

Suppose we have the following openapi-ts.config.ts :

export default defineConfig({
    client: "<my client>",
    input: "sample.yaml",
    output: {
        format: "prettier",
        path: "./app/api/client/",
        lint: "eslint"
    },
})

The generated code changes pretty significantly between the legacy fetch client and the current @hey-api/client-fetch client:

import { petsRead } from '~/api/client';

// with legacy `fetch`:
const pet = await petsRead({ petId: 1 });

// with current `@hey-api/client-fetch`:
const pet = await petsRead({ path: { petId: 1 }});

Note the extra path parameter here. Is there any way to avoid having to specify this on the modern fetch client? It's pretty verbose and clunky, since every single call requires { path: { ... } } now.

tervay avatar Jul 30 '24 17:07 tervay

@tervay Not at the moment, which is why it was created as a separate package. How many instances are we talking about?

mrlubos avatar Jul 30 '24 17:07 mrlubos

@mrlubos Probably 50ish endpoints, which is why I didn't just wrap it. If it was say five endpoints I'd just write some helper functions.

tervay avatar Jul 30 '24 17:07 tervay

That's going to be tough. Maybe your API has an explicit requirement for path parameters only, but what if you were to add query parameters in the future? The return types are also different btw, it's not just pet. I'd say gradual migration is still the easiest option here https://heyapi.vercel.app/openapi-ts/migrating.html#useoptions-true, but if you have a proposal for functionality, let me know (there are certainly ways to get it working with path params only APIs)

mrlubos avatar Jul 30 '24 18:07 mrlubos

I'd like to imagine it should be possible to semi-intelligently merge query/path parameters into one blob; that is, suppose we had:

export type PetsReadData = {
  path: {
    petId: number;
  };
  query: {
    full: boolean;
  };
};

It could be entirely possible to merge these into one options object:

export type MergedData = {
  petId: number;
  full: boolean;
};

If there's a name collision then... 🤷 ? Emit a warning? Fail? Not sure. Another option could be added to either merge or separate path/query param objects.

I suppose the library then needs to track which are query and which are path to send it off to fetch, though. Sounds annoying, so maybe this isn't easy.

tervay avatar Jul 30 '24 19:07 tervay

You described it perfectly in the last sentence. I will close this issue for now, but if anyone thinks there's a viable approach we should consider, we can re-open.

mrlubos avatar Aug 11 '24 11:08 mrlubos

And I'm bringing it back. I think resolving https://github.com/hey-api/openapi-ts/issues/558 correctly will unblock this issue

mrlubos avatar Aug 12 '24 09:08 mrlubos

Might need to handle useOptions=false in this release too

mrlubos avatar Aug 12 '24 09:08 mrlubos

Thanks again for this great project. We are also migrating our API generation to hey-api and are experiencing the same issue. I think that “body” should also be considered when merging these options, as this removes the implementation details from the function signature.

If there is a name conflict, then... 🤷 ? Issue a warning? Report an error? I'm not sure. Another option could be added to either merge or separate path/query parameter objects.

Perhaps this could be made configurable with an option like merge:

  • never: current behavior and default.
  • always: always merge options and throw an error in case of collisions.
  • if-possible: Only merge options if there are no collisions. Otherwise, keep body/query/path separate.

I don't know how easy it is to implement something like this, but I think it would improve the generated API.

We didn't have any major issues migrating to the new API. We used Copilot Agent mode and told it to run “npm run type-check” and fix all errors. We also gave it a brief description of the changes. This refactored 99% of API usage for us.

frank-swp avatar Sep 05 '25 10:09 frank-swp

v0.87.0 removed support for legacy clients. We now offer SDK options like paramsStructure: 'flat' to merge parameters. If there are other features you're still missing, please comment in relevant threads or open a new issue

mrlubos avatar Nov 04 '25 02:11 mrlubos