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

Generating code based on multiple OpenAPI schemas

Open frankPairs opened this issue 1 year ago • 7 comments

Description

Hey everyone!

I am facing a challenge using the library. On the project I am working on, we have several microservices, each of them with its own OpenAPI schema, which means that I have to deal with different .yaml files.

I am not sure if this is possible, but is there any way to declare several inputs instead of just one? kind of:

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

export default defineConfig({
  client: '@hey-api/client-axios',
  input: {
     "service1": '@generated/openapi-schemas/service1/service1.yaml',
     "service2": '@generated/openapi-schemas/service2/service2.yaml',
     ...
  },
  output: {
    format: 'prettier',
    lint: 'eslint',
    path: '@generated/api',
  },
})

I was checking the ClientConfig documentation, and it looks like the input property can be an object, but I am doing something wrong because it's not working as I expected.

I would appreciate any guidance.

Thanks a lot in advance!

frankPairs avatar Sep 18 '24 10:09 frankPairs

Hey @frankPairs, it can be an object, but it does not support what you're describing. This should be easy to add though. What's up with the "service1" naming? What's the expected output?

mrlubos avatar Sep 18 '24 11:09 mrlubos

My first intuition is that the files generated from @generated/openapi-schemas/service1/service1.yaml would be within the output folder using the same name. Just following the example describe above, it would look like:

Screenshot 2024-09-18 at 13 26 17

frankPairs avatar Sep 18 '24 11:09 frankPairs

I'd like to do something similar, but was thinking something like this would work, and found out it doesn't:

export default [
  defineConfig({
    client: '@hey-api/client-fetch',
    input: 'https://api1/openapi.json',
    output: 'src/clients/api1',
  }),
  defineConfig({
    client: '@hey-api/client-fetch',
    input: 'https://api2/openapi.json',
    output: 'src/clients/api2',
  }),
];

robert-lore avatar Sep 24 '24 20:09 robert-lore

Like said by @frankPairs, i will be very interested by this functionnality because on my project i have several swagger, and i would wish they are classified by input name in output directory. But actually, I have this issue if i launch the command

npm run openapi

Unexpected error occurred. Unsupported OpenAPI specification: { while in the documentation, the input type is a string or a Record<string, unknown>;

On project openapi-typescript-codegen https://github.com/ferdikoomen/openapi-typescript-codegen/tree/main?tab=readme-ov-file I was not bothered because I was running X commands (in the package.json) for X swagger to get the data generated. openapi -i http://localhost:8080/api/xxxx/swagger.json -o src/api/xxxx --client axios --indent 2

floleroy59000 avatar Oct 05 '24 09:10 floleroy59000

We also have multiple services we generate clients for. We simply have a base openapi-ts/defineConfig.ts, which prefixes all methods through methodeNameBuilder:

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

export const defineConfig = ({ input, name }: { input: string; name: string }) =>
  openapiTsDefineConfig({
    client: '@hey-api/client-fetch',
    experimentalParser: true,
    input: input,
    output: {
      path: `app/modules/api/${name}`,
      format: 'prettier',
      lint: 'eslint',
    },
    plugins: [
      {
        name: '@hey-api/types',
        enums: 'javascript',
      },
      {
        name: '@hey-api/services',
        methodNameBuilder: (operation) => {
          const id = operation.id;

          if (!id) {
            throw new Error('Missing operation.id');
          }

          return `${name}${id.charAt(0).toUpperCase() + id.slice(1)}`;
        },
      },
      '@tanstack/react-query',
    ],
  });

Then we have separate config file for each service, openapi-ts/management.config.ts:

import { defineConfig } from './config';

export default defineConfig({
 input: 'http://url-to-management-spec.openapi',
 name: 'management',
});

And openapi-ts/analysis.config.ts:

import { defineConfig } from './config';

export default defineConfig({
  input: 'http://url-to-analysis-spec.openapi',
  name: 'analysis',
});

Then we run them like:

openapi-ts --file openapi-ts/management.config.ts && openapi-ts --file openapi-ts/analysis.config.ts

Probably could be a bit smoother by calling them from node or similar, but it works.

KiwiKilian avatar Nov 18 '24 14:11 KiwiKilian

I made script

import { createClient, defaultPlugins } from '@hey-api/openapi-ts';

const services = {
  one: 'https://service.one-json',
  two: 'https://service.two-json',
  three: 'https://service.three-json',
};

Object.entries(services).forEach(([serviceName, url]) => {
  createClient({
    client: '@hey-api/client-fetch',
    input: url,
    output: {
      path: `src/http-client/types/${serviceName}`,
      format: 'prettier',
      lint: 'eslint',
      indexFile: false,
    },
    plugins: defaultPlugins.filter((plugin) => plugin !== '@hey-api/sdk'),
  });
});

then just call script and will generate multiple types

Kibo007 avatar Jan 22 '25 16:01 Kibo007

This is an essential feature imo. The advantage of OpenAPI is to be able to pull in any api you want and easily use as client in your code base. Having openapi-ts support only one client by default works if you have one backend and a frontend but breaks down as soon as you'd like to use micro services or any combination of openapi apis on your backend as well.

abegehr avatar May 21 '25 10:05 abegehr

any update on this?

rick1290 avatar Jun 11 '25 21:06 rick1290

any update on this?

https://github.com/hey-api/openapi-ts/issues/1056#issuecomment-2483291269 is working quite well for me, and sadly this is the reason why I didn't put more energy by contributing to it 😅 You should try that workaround, or you could think about to contribute and open a PR If you like you can call me to review your PR

Shinigami92 avatar Jun 12 '25 15:06 Shinigami92

Hey all, @carson2222 will be looking at this feature, he'll be your point of contact for getting this over the finish line 🐎

mrlubos avatar Aug 31 '25 21:08 mrlubos

I'm currently handling the multi-config feature. I added support for an array of objects to defineConfig function. Would this cover all your needs? Now i'm probably going to move on to allowing multiple inputs to one config

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

export default defineConfig([
  {
    input: "./openapi1.yaml",
    output: "src/client1",
  },
  {
    input: "./openapi1.yaml",
    output: "src/client2",
  },
  {
    input: "./openapi2.yaml",
    output: "src/client3",
  },
]);

carson2222 avatar Sep 04 '25 13:09 carson2222

hey @carson2222 ! I think your proposal looks nice to solve the problem that I described on this issue.

frankPairs avatar Sep 11 '25 19:09 frankPairs

I'm glad you like it. This and some more config features will be added soon once the https://github.com/hey-api/openapi-ts/pull/2602 gets merged 🫡

carson2222 avatar Sep 11 '25 19:09 carson2222

Hey @carson2222 any idea on when this will get merged? Would love to use this!

pachecoka avatar Oct 02 '25 09:10 pachecoka

@pachecoka can you try the preview in https://github.com/hey-api/openapi-ts/pull/2602#issuecomment-3281090710 and let us know if it works as expected? I wanted to clean up the internals a bit before merging, functionally it should just work already

mrlubos avatar Oct 02 '25 10:10 mrlubos

Hey @mrlubos,

I tried it out and it works. Only thing I noticed is that now in the generated code for client/client.gen.ts there is a function called makeSseFn and it makes use of BodyInit. This type was not being found and giving me an error, but adding the following to tsconfig made it work.

"lib": ["ESNext", "DOM"]

pachecoka avatar Oct 02 '25 13:10 pachecoka

This will be available in the next release. Looking forward to your feedback!

mrlubos avatar Oct 06 '25 03:10 mrlubos

Thanks for this feature, it's working. That's my adopted example, as prefixing the methods might still be necessary, but now in a single openapi-ts.config.ts:

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

export default defineConfig(
  [
    {
      input: 'https://data-service.localhost/api/docs/openapi',
      name: 'data',
    },
    {
      input: 'https://management-service.localhost/api/docs/openapi',
      name: 'management',
    },
  ].map(({ input, name }: { input: string; name: string }) => {
    const methodName = (id: string) => `${name}Api${id.charAt(0).toUpperCase() + id.slice(1)}`;

    return {
      input: input,
      output: {
        path: `src/modules/api/${name}`,
        lint: 'eslint',
      },
      parser: {
        hooks: {
          operations: {
            isQuery: (operation) => {
              if (operation.method === 'post') {
                return true;
              }
            },
          },
        },
      },
      plugins: [
        '@hey-api/client-fetch',
        {
          name: '@hey-api/typescript',
          enums: {
            mode: 'javascript',
            case: 'preserve',
          },
        },
        {
          name: '@hey-api/sdk',
          methodNameBuilder: (operation) => {
            const id = operation.id;

            if (!id) {
              throw new Error('Missing operation.id');
            }

            return methodName(id);
          },
        },
        {
          name: '@tanstack/react-query',
          queryOptions: {
            name: (id) => {
              return `${methodName(id)}Options`;
            },
          },
          queryKeys: {
            name: (id) => {
              return `${methodName(id)}QueryKey`;
            },
          },
          mutationOptions: {
            name: (id) => {
              return `${methodName(id)}Mutation`;
            },
          },
        },
        'zod',
      ],
    };
  }),
);

We run this with openapi-ts && prettier --write './src/modules/api/**/*.ts' --log-level error due to #2200. Also looking forward to #2579.

KiwiKilian avatar Oct 27 '25 09:10 KiwiKilian

@KiwiKilian this is starting to look clean!

mrlubos avatar Oct 27 '25 11:10 mrlubos