acacode / swagger-typescript-api

Generate the API Client for Fetch or Axios from an OpenAPI Specification
MIT License
3.38k stars 361 forks source link

swagger-typescript-api

Any questions you can ask here

Examples

All examples you can find here

Usage

Usage: sta [options]
Usage: swagger-typescript-api [options]
Usage: swagger-typescript-api generate-templates [options]

Options:
  -v, --version                 output the current version
  -p, --path <string>           path/url to swagger scheme
  -o, --output <string>         output path of typescript api file (default: "./")
  -n, --name <string>           name of output typescript api file (default: "Api.ts")
  -t, --templates <string>      path to folder containing templates
  -d, --default-as-success      use "default" response status code as success response too.
                                some swagger schemas use "default" response status code as success response type by default. (default: false)
  -r, --responses               generate additional information about request responses
                                also add typings for bad responses (default: false)
  --union-enums                 generate all "enum" types as union types (T1 | T2 | TN) (default: false)
  --add-readonly                generate readonly properties (default: false)
  --route-types                 generate type definitions for API routes (default: false)
  --no-client                   do not generate an API class
  --enum-names-as-values        use values in 'x-enumNames' as enum values (not only as keys) (default: false)
  --extract-request-params      extract request params to data contract (Also combine path params and query params into one object) (default: false)
  --extract-request-body        extract request body type to data contract (default: false)
  --extract-response-body       extract response body type to data contract (default: false)
  --extract-response-error      extract response error type to data contract (default: false)
  --modular                     generate separated files for http client, data contracts, and routes (default: false)
  --js                          generate js api module with declaration file (default: false)
  --module-name-index <number>  determines which path index should be used for routes separation (example: GET:/fruits/getFruit -> index:0 -> moduleName -> fruits) (default: 0)
  --module-name-first-tag       splits routes based on the first tag (default: false)
  --axios                       generate axios http client (default: false)
  --unwrap-response-data        unwrap the data item from the response (default: false)
  --disable-throw-on-error      Do not throw an error when response.ok is not true (default: false)
  --single-http-client          Ability to send HttpClient instance to Api constructor (default: false)
  --silent                      Output only errors to console (default: false)
  --default-response <type>     default type for empty response schema (default: "void")
  --type-prefix <string>        data contract name prefix (default: "")
  --type-suffix <string>        data contract name suffix (default: "")
  --clean-output                clean output folder before generate api. WARNING: May cause data loss (default: false)
  --api-class-name <string>     name of the api class (default: "Api")
  --patch                       fix up small errors in the swagger source definition (default: false)
  --debug                       additional information about processes inside this tool (default: false)
  --another-array-type          generate array types as Array<Type> (by default Type[]) (default: false)
  --sort-types                  sort fields and types (default: false)
  --sort-routes                 sort routes in alphabetical order (default: false)
  --custom-config <string>      custom config: primitiveTypeConstructs, hooks, ...  (default: "")
  --extract-enums               extract all enums from inline interface\type content to typescript enum construction (default: false)
  -h, --help                    display help for command

Commands:
  generate-templates              Generate ".ejs" templates needed for generate api
    -o, --output <string>         output path of generated templates
    -m, --modular                 generate templates needed to separate files for http client, data contracts, and routes (default: false)
    --http-client <string>        http client type (possible values: "fetch", "axios") (default: "fetch")
    -c, --clean-output            clean output folder before generate template. WARNING: May cause data loss (default: false)
    -r, --rewrite                 rewrite content in existing templates (default: false)
    --silent                      Output only errors to console (default: false)
    -h, --help                    display help for command

Also you can use npx:

npx swagger-typescript-api -p ./swagger.json -o ./src -n myApi.ts

You can use this package from nodejs:

import fs from "node:fs";
import path from "node:path";
import { generateApi, generateTemplates } from "swagger-typescript-api";

/* NOTE: all fields are optional expect one of `input`, `url`, `spec` */
generateApi({
  name: "MySuperbApi.ts",
  // set to `false` to prevent the tool from writing to disk
  output: path.resolve(process.cwd(), "./src/__generated__"),
  url: "http://api.com/swagger.json",
  input: path.resolve(process.cwd(), "./foo/swagger.json"),
  spec: {
    swagger: "2.0",
    info: {
      version: "1.0.0",
      title: "Swagger Petstore",
    },
    // ...
  },
  templates: path.resolve(process.cwd(), "./api-templates"),
  httpClientType: "axios", // or "fetch"
  defaultResponseAsSuccess: false,
  generateClient: true,
  generateRouteTypes: false,
  generateResponses: true,
  toJS: false,
  extractRequestParams: false,
  extractRequestBody: false,
  extractEnums: false,
  unwrapResponseData: false,
  prettier: {
    // By default prettier config is load from your project
    printWidth: 120,
    tabWidth: 2,
    trailingComma: "all",
    parser: "typescript",
  },
  defaultResponseType: "void",
  singleHttpClient: true,
  cleanOutput: false,
  enumNamesAsValues: false,
  moduleNameFirstTag: false,
  generateUnionEnums: false,
  typePrefix: "",
  typeSuffix: "",
  enumKeyPrefix: "",
  enumKeySuffix: "",
  addReadonly: false,
  sortTypes: false,
  sortRouters: false,
  extractingOptions: {
    requestBodySuffix: ["Payload", "Body", "Input"],
    requestParamsSuffix: ["Params"],
    responseBodySuffix: ["Data", "Result", "Output"],
    responseErrorSuffix: [
      "Error",
      "Fail",
      "Fails",
      "ErrorData",
      "HttpError",
      "BadResponse",
    ],
  },
  /** allow to generate extra files based with this extra templates, see more below */
  extraTemplates: [],
  anotherArrayType: false,
  fixInvalidTypeNamePrefix: "Type",
  fixInvalidEnumKeyPrefix: "Value",
  codeGenConstructs: (constructs) => ({
    ...constructs,
    RecordType: (key, value) => `MyRecord<key, value>`,
  }),
  primitiveTypeConstructs: (constructs) => ({
    ...constructs,
    string: {
      "date-time": "Date",
    },
  }),
  hooks: {
    onCreateComponent: (component) => {},
    onCreateRequestParams: (rawType) => {},
    onCreateRoute: (routeData) => {},
    onCreateRouteName: (routeNameInfo, rawRouteInfo) => {},
    onFormatRouteName: (routeInfo, templateRouteName) => {},
    onFormatTypeName: (typeName, rawTypeName, schemaType) => {},
    onInit: (configuration) => {},
    onPreParseSchema: (originalSchema, typeName, schemaType) => {},
    onParseSchema: (originalSchema, parsedSchema) => {},
    onPrepareConfig: (currentConfiguration) => {},
  },
})
  .then(({ files, configuration }) => {
    files.forEach(({ content, name }) => {
      fs.writeFile(path, content);
    });
  })
  .catch((e) => console.error(e));

generateTemplates({
  cleanOutput: false,
  output: PATH_TO_OUTPUT_DIR,
  httpClientType: "fetch",
  modular: false,
  silent: false,
  rewrite: false,
});

Options

--templates

This option needed for cases when you don't want to use the default swagger-typescript-api output structure You can create custom templates with extensions .ejs or .eta

Templates:

How to use it:

  1. copy swagger-typescript-api templates into your place in project
  2. add --templates PATH_TO_YOUR_TEMPLATES option
  3. modify ETA templates as you like

NOTE: Eta has special directive to render template in your Eta templates - includeFile(pathToTemplate, payload) If you want to use some default templates from this tool you can use path prefixes: @base, @default, @modular. @base - path to base templates @default - path to single api file templates @modular - path to multiple api files templates Examples: - includeFile("@base/data-contracts.ejs", { ...yourData, ...it }) - includeFile("@default/api.ejs", { ...yourData, ...it }) - includeFile("@default/procedure-call.ejs", { ...yourData, ...it }) - includeFile("@modular/api.ejs", { ...yourData, ...it }) - includeFile("@modular/procedure-call.ejs", { ...yourData, ...it }) - includeFile("@base/route-docs.ejs", { ...yourData, ...it }) - includeFile("@base/route-name.ejs", { ...yourData, ...it }) - includeFile("@base/route-type.ejs", { ...yourData, ...it }) - includeFile("@base/route-types.ejs", { ...yourData, ...it })

--module-name-index

This option should be used in cases when you have api with one global prefix like /api Example: GET:/api/fruits/getFruits POST:/api/fruits/addFruits GET:/api/vegetables/addVegetable with --module-name-index 0 Api class will have one property api When we change it to --module-name-index 1 then Api class have two properties fruits and vegetables

--module-name-first-tag

This option will group your API operations based on their first tag - mirroring how the Swagger UI groups displayed operations

extraTemplates (NodeJS option)

type (Record<string, any> & { name: string, path: string })[] This thing allow you to generate extra ts\js files based on extra templates (one extra template for one ts\js file) Example here

generate-templates command

This command allows you to generate source templates which using with option --templates

Modification internal codegen structs with NodeJS API:

You are able to modify TypeScript internal structs using for generating output with using generateApi options codeGenConstructs and primitiveTypeConstructs.

codeGenConstructs

This option has type (struct: CodeGenConstruct) => Partial<CodeGenConstruct>.

generateApi({
  // ...
  codeGenConstructs: (struct) => ({
    Keyword: {
      Number: "number",
      String: "string",
      Boolean: "boolean",
      Any: "any",
      Void: "void",
      Unknown: "unknown",
      Null: "null",
      Undefined: "undefined",
      Object: "object",
      File: "File",
      Date: "Date",
      Type: "type",
      Enum: "enum",
      Interface: "interface",
      Array: "Array",
      Record: "Record",
      Intersection: "&",
      Union: "|",
    },
    CodeGenKeyword: {
      UtilRequiredKeys: "UtilRequiredKeys",
    },
    /**
     * $A[] or Array<$A>
     */
    ArrayType: (content) => {
      if (this.anotherArrayType) {
        return `Array<${content}>`;
      }

      return `(${content})[]`;
    },
    /**
     * "$A"
     */
    StringValue: (content) => `"${content}"`,
    /**
     * $A
     */
    BooleanValue: (content) => `${content}`,
    /**
     * $A
     */
    NumberValue: (content) => `${content}`,
    /**
     * $A
     */
    NullValue: (content) => content,
    /**
     * $A1 | $A2
     */
    UnionType: (contents) => _.join(_.uniq(contents), ` | `),
    /**
     * ($A1)
     */
    ExpressionGroup: (content) => (content ? `(${content})` : ""),
    /**
     * $A1 & $A2
     */
    IntersectionType: (contents) => _.join(_.uniq(contents), ` & `),
    /**
     * Record<$A1, $A2>
     */
    RecordType: (key, value) => `Record<${key}, ${value}>`,
    /**
     * readonly $key?:$value
     */
    TypeField: ({ readonly, key, optional, value }) =>
      _.compact([
        readonly && "readonly ",
        key,
        optional && "?",
        ": ",
        value,
      ]).join(""),
    /**
     * [key: $A1]: $A2
     */
    InterfaceDynamicField: (key, value) => `[key: ${key}]: ${value}`,
    /**
     * $A1 = $A2
     */
    EnumField: (key, value) => `${key} = ${value}`,
    /**
     * $A0.key = $A0.value,
     * $A1.key = $A1.value,
     * $AN.key = $AN.value,
     */
    EnumFieldsWrapper: (contents) =>
      _.map(contents, ({ key, value }) => `  ${key} = ${value}`).join(",\n"),
    /**
     * {\n $A \n}
     */
    ObjectWrapper: (content) => `{\n${content}\n}`,
    /**
     * /** $A *\/
     */
    MultilineComment: (contents, formatFn) =>
      [
        ...(contents.length === 1
          ? [`/** ${contents[0]} */`]
          : ["/**", ...contents.map((content) => ` * ${content}`), " */"]),
      ].map((part) => `${formatFn ? formatFn(part) : part}\n`),
    /**
     * $A1<...$A2.join(,)>
     */
    TypeWithGeneric: (typeName, genericArgs) => {
      return `${typeName}${
        genericArgs.length ? `<${genericArgs.join(",")}>` : ""
      }`;
    },
  }),
});

For example, if you need to generate output Record<string, any> instead of object you can do it with using following code:

generateApi({
  // ...
  codeGenConstructs: (struct) => ({
    Keyword: {
      Object: "Record<string, any>",
    },
  }),
});

primitiveTypeConstructs

It is type mapper or translator swagger schema objects. primitiveTypeConstructs translates type/format schema fields to typescript structs. This option has type

type PrimitiveTypeStructValue =
  | string
  | ((
      schema: Record<string, any>,
      parser: import("./src/schema-parser/schema-parser").SchemaParser
    ) => string);

type PrimitiveTypeStruct = Record<
  "integer" | "number" | "boolean" | "object" | "file" | "string" | "array",
  | string
  | ({ $default: PrimitiveTypeStructValue } & Record<
      string,
      PrimitiveTypeStructValue
    >)
>;

declare const primitiveTypeConstructs: (
  struct: PrimitiveTypeStruct
) => Partial<PrimitiveTypeStruct>;

generateApi({
  // ...
  primitiveTypeConstructs: (struct) => ({
    integer: () => "number",
    number: () => "number",
    boolean: () => "boolean",
    object: () => "object",
    file: () => "File",
    string: {
      $default: () => "string",

      /** formats */
      binary: () => "File",
      file: () => "File",
      "date-time": () => "string",
      time: () => "string",
      date: () => "string",
      duration: () => "string",
      email: () => "string",
      "idn-email": () => "string",
      "idn-hostname": () => "string",
      ipv4: () => "string",
      ipv6: () => "string",
      uuid: () => "string",
      uri: () => "string",
      "uri-reference": () => "string",
      "uri-template": () => "string",
      "json-pointer": () => "string",
      "relative-json-pointer": () => "string",
      regex: () => "string",
    },
    array: (schema, parser) => {
      const content = parser.getInlineParseContent(schema.items);
      return parser.safeAddNullToType(schema, `(${content})[]`);
    },
  }),
});

For example, if you need to change "string"/"date-time" default output as string to Date you can do it with using following code:

generateApi({
  primitiveTypeConstructs: (struct) => ({
    string: {
      "date-time": "Date",
    },
  }),
});

See more about swagger schema type/format data here

Mass media

License

Licensed under the MIT License.