diff --git a/.changeset/rude-guests-try.md b/.changeset/rude-guests-try.md new file mode 100644 index 0000000000..2a3e715773 --- /dev/null +++ b/.changeset/rude-guests-try.md @@ -0,0 +1,6 @@ +--- +"@medusajs/openapi-typescript-codegen": patch +"@medusajs/medusa-oas-cli": patch +--- + +feat(codegen): openapi-typescript-codegen fork diff --git a/packages/medusa-core-utils/src/create-require-from-path.ts b/packages/medusa-core-utils/src/create-require-from-path.ts index be230a0340..b808defaa1 100644 --- a/packages/medusa-core-utils/src/create-require-from-path.ts +++ b/packages/medusa-core-utils/src/create-require-from-path.ts @@ -12,7 +12,6 @@ const fallback = (filename: string) => { } // Polyfill Node's `Module.createRequireFromPath` if not present (added in Node v10.12.0) -const createRequireFromPath = - Module.createRequire || Module.createRequireFromPath || fallback +const createRequireFromPath = Module.createRequire || fallback export default createRequireFromPath diff --git a/packages/oas/medusa-oas-cli/README.md b/packages/oas/medusa-oas-cli/README.md index b980fa8213..9b772f5189 100644 --- a/packages/oas/medusa-oas-cli/README.md +++ b/packages/oas/medusa-oas-cli/README.md @@ -19,6 +19,8 @@ N/A yarn medusa-oas ``` +--- + ### Command - `oas` This command will scan the `@medusajs/medusa` package in order to extract JSDoc OAS into a json file. @@ -69,4 +71,52 @@ Ignore OAS errors and attempt to output generated OAS files. ```bash yarn medusa-oas oas --force -``` \ No newline at end of file +``` + +--- + +### Command - `client` + +Will generate API client files from a given OAS file. + +#### `--src-file ` + +Specify the path to the OAS JSON file. + +`yarm medusa-oas client --src-file ./store.oas.json` + +#### `--name ` + +Namespace for the generated client. Usually `admin` or `store`. + +`yarm medusa-oas client --name admin` + +#### `--out-dir ` + +Specify in which directory should the files be outputted. Accepts relative and absolute path. It the directory doesn't +exist, it will be created. Defaults to `./`. + +`yarm medusa-oas client --out-dir ./client` + +#### `--type ` + +Client component types to generate. Accepts `all`, `types`, `client`, `hooks`. +Defaults to `all`. + +`yarn medusa-oas client --type types` + +#### `--types-packages ` + +Replace relative import statements by types package name. Mandatory when using `--type client` or `--type hooks`. + +#### `--client-packages ` + +Replace relative import statements by client package name. Mandatory when using `--type hooks`. + +`yarn medusa-oas client --type types` + +#### `--clean` + +Delete destination directory content before generating client. + +`yarn medusa-oas --clean` diff --git a/packages/oas/medusa-oas-cli/package.json b/packages/oas/medusa-oas-cli/package.json index d9ef807bd4..4aba185a59 100644 --- a/packages/oas/medusa-oas-cli/package.json +++ b/packages/oas/medusa-oas-cli/package.json @@ -32,8 +32,11 @@ }, "dependencies": { "@medusajs/medusa": "*", + "@medusajs/openapi-typescript-codegen": "*", "@readme/openapi-parser": "^2.4.0", + "@types/lodash": "^4.14.191", "commander": "^10.0.0", + "lodash": "^4.17.21", "openapi3-ts": "^3.1.2", "swagger-inline": "^6.1.0" } diff --git a/packages/oas/medusa-oas-cli/src/command-client.ts b/packages/oas/medusa-oas-cli/src/command-client.ts new file mode 100644 index 0000000000..0ca13dfa61 --- /dev/null +++ b/packages/oas/medusa-oas-cli/src/command-client.ts @@ -0,0 +1,175 @@ +import path from "path" +import { + generate, + HttpClient, + Indent, + PackageNames, +} from "@medusajs/openapi-typescript-codegen" +import { upperFirst } from "lodash" +import fs, { mkdir, readFile } from "fs/promises" +import { OpenAPIObject } from "openapi3-ts" +import { Command, Option, OptionValues } from "commander" + +/** + * CLI Command declaration + */ +export const commandName = "client" +export const commandDescription = "Generate API clients from OAS." +export const commandOptions: Option[] = [ + new Option( + "-t, --type ", + "Namespace for the generated client. Usually `admin` or `store`." + ).makeOptionMandatory(), + + new Option( + "-s, --src-file ", + "Path to source OAS JSON file." + ).makeOptionMandatory(), + + new Option( + "-o, --out-dir ", + "Output directory for generated client files." + ).default(path.resolve(process.cwd(), "client")), + + new Option( + "-c, --component ", + "Client component types to generate." + ) + .choices(["all", "types", "client", "hooks"]) + .default("all"), + + new Option( + "--types-package ", + "Replace relative import statements by types package name." + ), + + new Option( + "--client-package ", + "Replace relative import statements by client package name." + ), + + new Option( + "--clean", + "Delete destination directory content before generating client." + ), +] + +export function getCommand() { + const command = new Command(commandName) + command.description(commandDescription) + for (const opt of commandOptions) { + command.addOption(opt) + } + command.action(async (options) => await execute(options)) + return command +} + +/** + * Main + */ +export async function execute(cliParams: OptionValues) { + /** + * Process CLI options + */ + if ( + ["client", "hooks"].includes(cliParams.component) && + !cliParams.typesPackage + ) { + throw new Error( + `--types-package must be declared when using --component=${cliParams.component}` + ) + } + if (cliParams.component === "hooks" && !cliParams.clientPackage) { + throw new Error( + `--client-package must be declared when using --component=${cliParams.component}` + ) + } + + const shouldClean = !!cliParams.clean + const srcFile = path.resolve(cliParams.srcFile) + const outDir = path.resolve(cliParams.outDir) + const apiName = cliParams.type + const packageNames: PackageNames = { + models: cliParams.typesPackage, + client: cliParams.clientPackage, + } + const exportComponent = cliParams.component + + /** + * Command execution + */ + console.log(`🟣 Generating client - ${apiName} - ${exportComponent}`) + + if (shouldClean) { + console.log(`🟠 Cleaning output directory`) + await fs.rm(outDir, { recursive: true, force: true }) + } + await mkdir(outDir, { recursive: true }) + + const oas = await getOASFromFile(srcFile) + await generateClientSDK(oas, outDir, apiName, exportComponent, packageNames) + + console.log( + `🟢 Client generated - ${apiName} - ${exportComponent} - ${outDir}` + ) +} + +/** + * Methods + */ +const getOASFromFile = async (jsonFile: string): Promise => { + const jsonString = await readFile(jsonFile, "utf8") + return JSON.parse(jsonString) +} + +const generateClientSDK = async ( + oas: OpenAPIObject, + targetDir: string, + apiName: string, + exportComponent: "all" | "types" | "client" | "hooks", + packageNames: PackageNames = {} +) => { + const exports = { + exportCore: false, + exportServices: false, + exportModels: false, + exportHooks: false, + } + + switch (exportComponent) { + case "types": + exports.exportModels = true + break + case "client": + exports.exportCore = true + exports.exportServices = true + break + case "hooks": + exports.exportHooks = true + break + default: + exports.exportCore = true + exports.exportServices = true + exports.exportModels = true + exports.exportHooks = true + } + + await generate({ + input: oas, + output: targetDir, + httpClient: HttpClient.AXIOS, + useOptions: true, + useUnionTypes: true, + exportCore: exports.exportCore, + exportServices: exports.exportServices, + exportModels: exports.exportModels, + exportHooks: exports.exportHooks, + exportSchemas: false, + indent: Indent.SPACE_2, + postfixServices: "Service", + postfixModels: "", + clientName: `Medusa${upperFirst(apiName)}`, + request: undefined, + packageNames, + }) +} diff --git a/packages/oas/medusa-oas-cli/src/index.ts b/packages/oas/medusa-oas-cli/src/index.ts index acd77ea692..2235620a64 100644 --- a/packages/oas/medusa-oas-cli/src/index.ts +++ b/packages/oas/medusa-oas-cli/src/index.ts @@ -2,6 +2,7 @@ import { Command } from "commander" import { getCommand as oasGetCommand } from "./command-oas" +import { getCommand as clientGetCommand } from "./command-client" const run = async () => { const program = new Command() @@ -11,6 +12,11 @@ const run = async () => { */ program.addCommand(oasGetCommand()) + /** + * Alias to command-client.ts + */ + program.addCommand(clientGetCommand()) + /** * Run CLI */ diff --git a/packages/oas/openapi-typescript-codegen/.gitignore b/packages/oas/openapi-typescript-codegen/.gitignore new file mode 100644 index 0000000000..1a8a9e80b9 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/.gitignore @@ -0,0 +1,7 @@ +/dist +node_modules +.DS_store +.env* +.env +*.sql +/bin \ No newline at end of file diff --git a/packages/oas/openapi-typescript-codegen/.prettierignore b/packages/oas/openapi-typescript-codegen/.prettierignore new file mode 100644 index 0000000000..5acb4c912f --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/.prettierignore @@ -0,0 +1 @@ +*.hbs \ No newline at end of file diff --git a/packages/oas/openapi-typescript-codegen/CHANGELOG.md b/packages/oas/openapi-typescript-codegen/CHANGELOG.md new file mode 100644 index 0000000000..8e92804374 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/CHANGELOG.md @@ -0,0 +1,3 @@ +# @medusajs/openapi-typescript-codegen + +## 0.1.0 diff --git a/packages/oas/openapi-typescript-codegen/README.md b/packages/oas/openapi-typescript-codegen/README.md new file mode 100644 index 0000000000..e0d763158d --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/README.md @@ -0,0 +1,39 @@ +# OpenAPI Typescript Codegen - 0.1.0 - experimental + +Node.js library that generates Typescript clients based on the OpenAPI specification. + +## About this fork + +This package is a significantly customized fork of +the amazing npm [openapi-typescript-codegen](https://github.com/ferdikoomen/openapi-typescript-codegen) package 💜. + +### Brief reasoning + +We wanted a level of customization that was not achievable through the source package's interface. +We started with a conventional fork but the development workflow was hindering our ability to iterate quickly. +We decided to fold the package within our monorepo to hopefully accelerate development and innovation. + +### Noteworthy differences + +* Added ability to generate React hooks in the style of `medusa-react`. +* Added access to the raw OAS on parsed element from the parser. +* Added access `operationId` from path. +* Added access `x-codegen` from path. +* Added renaming of service method when `x-codegen.method` is declared. +* Added bundling of query params into a typed object when `x-codegen.queryParams` is declared. +* Added parameterization of import path for the client and models. +* Added generated `index.ts` files in directories to simplify imports. +* Updated npm dependencies' version to match existing versions found in the monorepo. +* Updated coding style to match the convention of the monorepo. +* Removed support for Angular client generation. +* Removed support for OAS v2 parsing. +* Removed documentation. +* Removed tests (temporarily). + +## Install + +`yarn add --dev @medusajs/openapi-typescript-codegen` + +## How to use + +See `@medusajs/medusa-oas-cli` for usage. diff --git a/packages/oas/openapi-typescript-codegen/babel.config.json b/packages/oas/openapi-typescript-codegen/babel.config.json new file mode 100644 index 0000000000..ff5c1c77bc --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/babel.config.json @@ -0,0 +1,18 @@ +{ + "presets": [ + [ + "@babel/preset-env", + { + "targets": { + "node": "12" + } + } + ], + [ + "@babel/preset-typescript", + { + "onlyRemoveTypeImports": true + } + ] + ] +} diff --git a/packages/oas/openapi-typescript-codegen/package.json b/packages/oas/openapi-typescript-codegen/package.json new file mode 100644 index 0000000000..1a5b855d4d --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/package.json @@ -0,0 +1,62 @@ +{ + "name": "@medusajs/openapi-typescript-codegen", + "version": "0.1.0", + "description": "Library that generates Typescript clients based on the OpenAPI specification.", + "main": "dist/index.js", + "types": "types/index.d.ts", + "files": [ + "dist/index.js", + "types/index.d.ts" + ], + "repository": { + "type": "git", + "url": "https://github.com/medusajs/medusa", + "directory": "packages/oas/openapi-typescript-codegen" + }, + "author": "Medusa", + "contributors": [ + "Ferdi Koomen" + ], + "license": "MIT", + "scripts": { + "prepare": "cross-env NODE_ENV=production yarn run release", + "build": "rollup --config --environment NODE_ENV:development", + "release": "rollup --config --environment NODE_ENV:production", + "test": "jest --passWithNoTests", + "test:unit": "jest --passWithNoTests" + }, + "dependencies": { + "camelcase": "^6.3.0", + "commander": "^9.4.1", + "fs-extra": "^10.1.0", + "handlebars": "^4.7.7", + "json-schema-ref-parser": "^9.0.9", + "pascalcase": "^2.0.0" + }, + "devDependencies": { + "@babel/cli": "7.14.3", + "@babel/core": "7.14.3", + "@babel/preset-env": "7.11.5", + "@babel/preset-typescript": "7.18.6", + "@rollup/plugin-commonjs": "24.0.0", + "@rollup/plugin-node-resolve": "15.0.1", + "@rollup/plugin-typescript": "9.0.2", + "@types/fs-extra": "^9.0.12", + "@types/jest": "27.5.0", + "@types/node": "18.11.9", + "@types/node-fetch": "2.6.2", + "@types/pascalcase": "^1.0.1", + "@types/qs": "6.9.7", + "abort-controller": "3.0.0", + "axios": "1.2.0", + "form-data": "4.0.0", + "jest": "26.6.3", + "jest-cli": "26.6.3", + "node-fetch": "2.6.7", + "qs": "6.10.3", + "rollup": "3.9.1", + "rollup-plugin-terser": "7.0.2", + "tslib": "2.3.1", + "typescript": "4.9.5" + } +} diff --git a/packages/oas/openapi-typescript-codegen/rollup.config.mjs b/packages/oas/openapi-typescript-codegen/rollup.config.mjs new file mode 100644 index 0000000000..8236101b73 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/rollup.config.mjs @@ -0,0 +1,83 @@ +import commonjs from "@rollup/plugin-commonjs" +import { nodeResolve } from "@rollup/plugin-node-resolve" +import typescript from "@rollup/plugin-typescript" +import { readFileSync } from "fs" +import handlebars from "handlebars" +import { dirname, extname, resolve } from "path" +import { terser } from "rollup-plugin-terser" + +const { precompile } = handlebars + +/** + * Custom plugin to parse handlebar imports and precompile + * the template on the fly. This reduces runtime by about + * half on large projects. + */ +const handlebarsPlugin = () => ({ + resolveId: (file, importer) => { + if (extname(file) === ".hbs") { + return resolve(dirname(importer), file) + } + return null + }, + load: (file) => { + if (extname(file) === ".hbs") { + const template = readFileSync(file, "utf8").toString().trim() + const templateSpec = precompile(template, { + strict: true, + noEscape: true, + preventIndent: true, + knownHelpersOnly: true, + knownHelpers: { + ifdef: true, + equals: true, + notEquals: true, + containsSpaces: true, + union: true, + intersection: true, + enumerator: true, + escapeComment: true, + escapeDescription: true, + camelCase: true, + pascalCase: true, + }, + }) + return `export default ${templateSpec};` + } + return null + }, +}) + +const getPlugins = () => { + const plugins = [ + nodeResolve(), + commonjs({ + sourceMap: false, + }), + handlebarsPlugin(), + typescript({ + module: "esnext", + }), + ] + if (process.env.NODE_ENV === "development") { + return plugins + } + return [...plugins, terser()] +} + +export default { + input: "./src/index.ts", + output: { + exports: "named", + file: "./dist/index.js", + format: "cjs", + }, + external: [ + "camelcase", + "commander", + "fs-extra", + "handlebars", + "json-schema-ref-parser", + ], + plugins: getPlugins(), +} diff --git a/packages/oas/openapi-typescript-codegen/src/HttpClient.ts b/packages/oas/openapi-typescript-codegen/src/HttpClient.ts new file mode 100644 index 0000000000..960d38677c --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/HttpClient.ts @@ -0,0 +1,6 @@ +export enum HttpClient { + FETCH = "fetch", + XHR = "xhr", + NODE = "node", + AXIOS = "axios", +} diff --git a/packages/oas/openapi-typescript-codegen/src/Indent.ts b/packages/oas/openapi-typescript-codegen/src/Indent.ts new file mode 100644 index 0000000000..19b5fe093b --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/Indent.ts @@ -0,0 +1,5 @@ +export enum Indent { + SPACE_4 = "4", + SPACE_2 = "2", + TAB = "tab", +} diff --git a/packages/oas/openapi-typescript-codegen/src/client/interfaces/Client.d.ts b/packages/oas/openapi-typescript-codegen/src/client/interfaces/Client.d.ts new file mode 100644 index 0000000000..fb90e78bcc --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/client/interfaces/Client.d.ts @@ -0,0 +1,9 @@ +import type { Model } from "./Model" +import type { Service } from "./Service" + +export interface Client { + version: string + server: string + models: Model[] + services: Service[] +} diff --git a/packages/oas/openapi-typescript-codegen/src/client/interfaces/Enum.d.ts b/packages/oas/openapi-typescript-codegen/src/client/interfaces/Enum.d.ts new file mode 100644 index 0000000000..7f1fc0f79f --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/client/interfaces/Enum.d.ts @@ -0,0 +1,6 @@ +export interface Enum { + name: string + value: string + type: string + description: string | null +} diff --git a/packages/oas/openapi-typescript-codegen/src/client/interfaces/Model.d.ts b/packages/oas/openapi-typescript-codegen/src/client/interfaces/Model.d.ts new file mode 100644 index 0000000000..a3f46af4b2 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/client/interfaces/Model.d.ts @@ -0,0 +1,27 @@ +import type { Enum } from "./Enum" +import type { Schema } from "./Schema" + +export interface Model extends Schema { + name: string + export: + | "reference" + | "generic" + | "enum" + | "array" + | "dictionary" + | "interface" + | "one-of" + | "any-of" + | "all-of" + type: string + base: string + template: string | null + link: Model | null + description: string | null + deprecated?: boolean + default?: string + imports: string[] + enum: Enum[] + enums: Model[] + properties: Model[] +} diff --git a/packages/oas/openapi-typescript-codegen/src/client/interfaces/ModelComposition.d.ts b/packages/oas/openapi-typescript-codegen/src/client/interfaces/ModelComposition.d.ts new file mode 100644 index 0000000000..e7a801a4a1 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/client/interfaces/ModelComposition.d.ts @@ -0,0 +1,8 @@ +import type { Model } from "./Model" + +export interface ModelComposition { + type: "one-of" | "any-of" | "all-of" + imports: string[] + enums: Model[] + properties: Model[] +} diff --git a/packages/oas/openapi-typescript-codegen/src/client/interfaces/Operation.d.ts b/packages/oas/openapi-typescript-codegen/src/client/interfaces/Operation.d.ts new file mode 100644 index 0000000000..55e23a20a9 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/client/interfaces/Operation.d.ts @@ -0,0 +1,19 @@ +import type { OperationError } from "./OperationError" +import type { OperationParameters } from "./OperationParameters" +import type { OperationResponse } from "./OperationResponse" +import type { OperationCodegen } from "./OperationCodegen" + +export interface Operation extends OperationParameters { + service: string + name: string + operationId: string | null + summary: string | null + description: string | null + deprecated: boolean + method: string + path: string + errors: OperationError[] + results: OperationResponse[] + responseHeader: string | null + codegen: OperationCodegen +} diff --git a/packages/oas/openapi-typescript-codegen/src/client/interfaces/OperationCodegen.d.ts b/packages/oas/openapi-typescript-codegen/src/client/interfaces/OperationCodegen.d.ts new file mode 100644 index 0000000000..6fbf9734e7 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/client/interfaces/OperationCodegen.d.ts @@ -0,0 +1,4 @@ +export interface OperationCodegen { + method?: string + queryParams?: string +} diff --git a/packages/oas/openapi-typescript-codegen/src/client/interfaces/OperationError.d.ts b/packages/oas/openapi-typescript-codegen/src/client/interfaces/OperationError.d.ts new file mode 100644 index 0000000000..82639c1dce --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/client/interfaces/OperationError.d.ts @@ -0,0 +1,4 @@ +export interface OperationError { + code: number + description: string +} diff --git a/packages/oas/openapi-typescript-codegen/src/client/interfaces/OperationParameter.d.ts b/packages/oas/openapi-typescript-codegen/src/client/interfaces/OperationParameter.d.ts new file mode 100644 index 0000000000..ac7a023a9c --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/client/interfaces/OperationParameter.d.ts @@ -0,0 +1,10 @@ +import type { Model } from "./Model" +import { OpenApiParameter } from "../../openApi/v3/interfaces/OpenApiParameter" +import { OpenApiRequestBody } from "../../openApi/v3/interfaces/OpenApiRequestBody" + +export interface OperationParameter extends Model { + spec: OpenApiParameter | OpenApiRequestBody + in: "path" | "query" | "header" | "formData" | "body" | "cookie" + prop: string + mediaType: string | null +} diff --git a/packages/oas/openapi-typescript-codegen/src/client/interfaces/OperationParameters.d.ts b/packages/oas/openapi-typescript-codegen/src/client/interfaces/OperationParameters.d.ts new file mode 100644 index 0000000000..7237be2ace --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/client/interfaces/OperationParameters.d.ts @@ -0,0 +1,12 @@ +import type { OperationParameter } from "./OperationParameter" + +export interface OperationParameters { + imports: string[] + parameters: OperationParameter[] + parametersPath: OperationParameter[] + parametersQuery: OperationParameter[] + parametersForm: OperationParameter[] + parametersCookie: OperationParameter[] + parametersHeader: OperationParameter[] + parametersBody: OperationParameter | null +} diff --git a/packages/oas/openapi-typescript-codegen/src/client/interfaces/OperationResponse.d.ts b/packages/oas/openapi-typescript-codegen/src/client/interfaces/OperationResponse.d.ts new file mode 100644 index 0000000000..5be335cd32 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/client/interfaces/OperationResponse.d.ts @@ -0,0 +1,8 @@ +import type { Model } from "./Model" +import { OpenApiResponse } from "../../openApi/v3/interfaces/OpenApiResponse" + +export interface OperationResponse extends Model { + spec: OpenApiResponse + in: "response" | "header" + code: number +} diff --git a/packages/oas/openapi-typescript-codegen/src/client/interfaces/Schema.d.ts b/packages/oas/openapi-typescript-codegen/src/client/interfaces/Schema.d.ts new file mode 100644 index 0000000000..bff11c4770 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/client/interfaces/Schema.d.ts @@ -0,0 +1,34 @@ +import { OpenApiSchema } from "../../openApi/v3/interfaces/OpenApiSchema" + +export interface Schema { + spec: OpenApiSchema + isDefinition: boolean + isReadOnly: boolean + isRequired: boolean + isNullable: boolean + format?: + | "int32" + | "int64" + | "float" + | "double" + | "string" + | "boolean" + | "byte" + | "binary" + | "date" + | "date-time" + | "password" + maximum?: number + exclusiveMaximum?: boolean + minimum?: number + exclusiveMinimum?: boolean + multipleOf?: number + maxLength?: number + minLength?: number + pattern?: string + maxItems?: number + minItems?: number + uniqueItems?: boolean + maxProperties?: number + minProperties?: number +} diff --git a/packages/oas/openapi-typescript-codegen/src/client/interfaces/Service.d.ts b/packages/oas/openapi-typescript-codegen/src/client/interfaces/Service.d.ts new file mode 100644 index 0000000000..b1ee601f50 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/client/interfaces/Service.d.ts @@ -0,0 +1,7 @@ +import type { Operation } from "./Operation" + +export interface Service { + name: string + operations: Operation[] + imports: string[] +} diff --git a/packages/oas/openapi-typescript-codegen/src/client/interfaces/Type.d.ts b/packages/oas/openapi-typescript-codegen/src/client/interfaces/Type.d.ts new file mode 100644 index 0000000000..6180b565d0 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/client/interfaces/Type.d.ts @@ -0,0 +1,7 @@ +export interface Type { + type: string + base: string + template: string | null + imports: string[] + isNullable: boolean +} diff --git a/packages/oas/openapi-typescript-codegen/src/index.ts b/packages/oas/openapi-typescript-codegen/src/index.ts new file mode 100644 index 0000000000..446f834ba8 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/index.ts @@ -0,0 +1,116 @@ +import { HttpClient } from "./HttpClient" +import { Indent } from "./Indent" +import { parse } from "./openApi/v3" +import { getOpenApiSpec } from "./utils/getOpenApiSpec" +import { getOpenApiVersion } from "./utils/getOpenApiVersion" +import { isString } from "./utils/isString" +import { postProcessClient } from "./utils/postProcessClient" +import { registerHandlebarTemplates } from "./utils/registerHandlebarTemplates" +import { writeClient } from "./utils/writeClient" + +export { HttpClient } from "./HttpClient" +export { Indent } from "./Indent" + +export type PackageNames = { + models?: string + client?: string +} + +export type Options = { + input: string | Record + output: string + httpClient?: HttpClient + clientName?: string + useOptions?: boolean + useUnionTypes?: boolean + exportCore?: boolean + exportServices?: boolean + exportModels?: boolean + exportHooks?: boolean + exportSchemas?: boolean + indent?: Indent + packageNames?: PackageNames + postfixServices?: string + postfixModels?: string + request?: string + write?: boolean +} + +/** + * Generate the OpenAPI client. This method will read the OpenAPI specification and based on the + * given language it will generate the client, including the typed models, validation schemas, + * service layer, etc. + * @param input The relative location of the OpenAPI spec + * @param output The relative location of the output directory + * @param httpClient The selected httpClient (fetch, xhr, node or axios) + * @param clientName Custom client class name + * @param useOptions Use options or arguments functions + * @param useUnionTypes Use union types instead of enums + * @param exportCore Generate core client classes + * @param exportServices Generate services + * @param exportModels Generate models + * @param exportHooks Generate hooks + * @param exportSchemas Generate schemas + * @param indent Indentation options (4, 2 or tab) + * @param packageNames Package name to use in import statements. + * @param postfixServices Service name postfix + * @param postfixModels Model name postfix + * @param request Path to custom request file + * @param write Write the files to disk (true or false) + */ +export const generate = async ({ + input, + output, + httpClient = HttpClient.FETCH, + clientName, + useOptions = false, + useUnionTypes = false, + exportCore = true, + exportServices = true, + exportModels = true, + exportHooks = true, + exportSchemas = false, + indent = Indent.SPACE_4, + packageNames = {}, + postfixServices = "Service", + postfixModels = "", + request, + write = true, +}: Options): Promise => { + const openApi = isString(input) ? await getOpenApiSpec(input) : input + const openApiVersion = getOpenApiVersion(openApi) + const templates = registerHandlebarTemplates({ + httpClient, + useUnionTypes, + useOptions, + }) + + const client = parse(openApi) + const clientFinal = postProcessClient(client) + if (write) { + await writeClient( + clientFinal, + templates, + output, + httpClient, + useOptions, + useUnionTypes, + exportCore, + exportServices, + exportModels, + exportHooks, + exportSchemas, + indent, + packageNames, + postfixServices, + postfixModels, + clientName, + request + ) + } +} + +export default { + HttpClient, + generate, +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/index.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/index.ts new file mode 100644 index 0000000000..01f47dccb1 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/index.ts @@ -0,0 +1,20 @@ +import type { Client } from "../../client/interfaces/Client" +import type { OpenApi } from "./interfaces/OpenApi" +import { getModels } from "./parser/getModels" +import { getServer } from "./parser/getServer" +import { getServices } from "./parser/getServices" +import { getServiceVersion } from "./parser/getServiceVersion" + +/** + * Parse the OpenAPI specification to a Client model that contains + * all the models, services and schema's we should output. + * @param openApi The OpenAPI spec that we have loaded from disk. + */ +export const parse = (openApi: OpenApi): Client => { + const version = getServiceVersion(openApi.info.version) + const server = getServer(openApi) + const models = getModels(openApi) + const services = getServices(openApi) + + return { version, server, models, services } +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/Extensions/WithEnumExtension.d.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/Extensions/WithEnumExtension.d.ts new file mode 100644 index 0000000000..f11828f332 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/Extensions/WithEnumExtension.d.ts @@ -0,0 +1,4 @@ +export interface WithEnumExtension { + "x-enum-varnames"?: string[] + "x-enum-descriptions"?: string[] +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApi.d.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApi.d.ts new file mode 100644 index 0000000000..d5fb333dbb --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApi.d.ts @@ -0,0 +1,21 @@ +import type { OpenApiComponents } from "./OpenApiComponents" +import type { OpenApiExternalDocs } from "./OpenApiExternalDocs" +import type { OpenApiInfo } from "./OpenApiInfo" +import type { OpenApiPaths } from "./OpenApiPaths" +import type { OpenApiSecurityRequirement } from "./OpenApiSecurityRequirement" +import type { OpenApiServer } from "./OpenApiServer" +import type { OpenApiTag } from "./OpenApiTag" + +/** + * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md + */ +export interface OpenApi { + openapi: string + info: OpenApiInfo + servers?: OpenApiServer[] + paths: OpenApiPaths + components?: OpenApiComponents + security?: OpenApiSecurityRequirement[] + tags?: OpenApiTag[] + externalDocs?: OpenApiExternalDocs +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiCallback.d.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiCallback.d.ts new file mode 100644 index 0000000000..d3f8d0977d --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiCallback.d.ts @@ -0,0 +1,9 @@ +import type { OpenApiPath } from "./OpenApiPath" +import type { OpenApiReference } from "./OpenApiReference" + +/** + * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#callbackObject + */ +export interface OpenApiCallback extends OpenApiReference { + [key: string]: OpenApiPath +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiComponents.d.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiComponents.d.ts new file mode 100644 index 0000000000..a1f9e8dc0d --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiComponents.d.ts @@ -0,0 +1,25 @@ +import type { Dictionary } from "../../../utils/types" +import type { OpenApiCallback } from "./OpenApiCallback" +import type { OpenApiExample } from "./OpenApiExample" +import type { OpenApiHeader } from "./OpenApiHeader" +import type { OpenApiLink } from "./OpenApiLink" +import type { OpenApiParameter } from "./OpenApiParameter" +import type { OpenApiRequestBody } from "./OpenApiRequestBody" +import type { OpenApiResponses } from "./OpenApiResponses" +import type { OpenApiSchema } from "./OpenApiSchema" +import type { OpenApiSecurityScheme } from "./OpenApiSecurityScheme" + +/** + * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#componentsObject + */ +export interface OpenApiComponents { + schemas?: Dictionary + responses?: Dictionary + parameters?: Dictionary + examples?: Dictionary + requestBodies?: Dictionary + headers?: Dictionary + securitySchemes?: Dictionary + links?: Dictionary + callbacks?: Dictionary +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiContact.d.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiContact.d.ts new file mode 100644 index 0000000000..a74658319b --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiContact.d.ts @@ -0,0 +1,8 @@ +/** + * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#contactObject + */ +export interface OpenApiContact { + name?: string + url?: string + email?: string +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiDiscriminator.d.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiDiscriminator.d.ts new file mode 100644 index 0000000000..064e289e77 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiDiscriminator.d.ts @@ -0,0 +1,9 @@ +import type { Dictionary } from "../../../utils/types" + +/** + * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#discriminatorObject + */ +export interface OpenApiDiscriminator { + propertyName: string + mapping?: Dictionary +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiEncoding.d.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiEncoding.d.ts new file mode 100644 index 0000000000..eda5d4c0a4 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiEncoding.d.ts @@ -0,0 +1,13 @@ +import type { Dictionary } from "../../../utils/types" +import type { OpenApiHeader } from "./OpenApiHeader" + +/** + * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#encodingObject + */ +export interface OpenApiEncoding { + contentType?: string + headers?: Dictionary + style?: string + explode?: boolean + allowReserved?: boolean +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiExample.d.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiExample.d.ts new file mode 100644 index 0000000000..7f5f92db20 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiExample.d.ts @@ -0,0 +1,11 @@ +import type { OpenApiReference } from "./OpenApiReference" + +/** + * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#exampleObject + */ +export interface OpenApiExample extends OpenApiReference { + summary?: string + description?: string + value?: any + externalValue?: string +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiExternalDocs.d.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiExternalDocs.d.ts new file mode 100644 index 0000000000..b522ef272e --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiExternalDocs.d.ts @@ -0,0 +1,7 @@ +/** + * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#externalDocumentationObject + */ +export interface OpenApiExternalDocs { + description?: string + url: string +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiHeader.d.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiHeader.d.ts new file mode 100644 index 0000000000..470236351e --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiHeader.d.ts @@ -0,0 +1,20 @@ +import type { Dictionary } from "../../../utils/types" +import type { OpenApiExample } from "./OpenApiExample" +import type { OpenApiReference } from "./OpenApiReference" +import type { OpenApiSchema } from "./OpenApiSchema" + +/** + * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#headerObject + */ +export interface OpenApiHeader extends OpenApiReference { + description?: string + required?: boolean + deprecated?: boolean + allowEmptyValue?: boolean + style?: string + explode?: boolean + allowReserved?: boolean + schema?: OpenApiSchema + example?: any + examples?: Dictionary +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiInfo.d.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiInfo.d.ts new file mode 100644 index 0000000000..25ace57a4e --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiInfo.d.ts @@ -0,0 +1,14 @@ +import type { OpenApiContact } from "./OpenApiContact" +import type { OpenApiLicense } from "./OpenApiLicense" + +/** + * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#infoObject + */ +export interface OpenApiInfo { + title: string + description?: string + termsOfService?: string + contact?: OpenApiContact + license?: OpenApiLicense + version: string +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiLicense.d.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiLicense.d.ts new file mode 100644 index 0000000000..e34ea2c14b --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiLicense.d.ts @@ -0,0 +1,7 @@ +/** + * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#licenseObject + */ +export interface OpenApiLicense { + name: string + url?: string +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiLink.d.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiLink.d.ts new file mode 100644 index 0000000000..34428c5f84 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiLink.d.ts @@ -0,0 +1,15 @@ +import type { Dictionary } from "../../../utils/types" +import type { OpenApiReference } from "./OpenApiReference" +import type { OpenApiServer } from "./OpenApiServer" + +/** + * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#linkObject + */ +export interface OpenApiLink extends OpenApiReference { + operationRef?: string + operationId?: string + parameters?: Dictionary + requestBody?: any + description?: string + server?: OpenApiServer +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiMediaType.d.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiMediaType.d.ts new file mode 100644 index 0000000000..5398c17cef --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiMediaType.d.ts @@ -0,0 +1,15 @@ +import type { Dictionary } from "../../../utils/types" +import type { OpenApiEncoding } from "./OpenApiEncoding" +import type { OpenApiExample } from "./OpenApiExample" +import type { OpenApiReference } from "./OpenApiReference" +import type { OpenApiSchema } from "./OpenApiSchema" + +/** + * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#mediaTypeObject + */ +export interface OpenApiMediaType extends OpenApiReference { + schema?: OpenApiSchema + example?: any + examples?: Dictionary + encoding?: Dictionary +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiOAuthFlow.d.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiOAuthFlow.d.ts new file mode 100644 index 0000000000..eaa7727bda --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiOAuthFlow.d.ts @@ -0,0 +1,11 @@ +import type { Dictionary } from "../../../utils/types" + +/** + * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#oauthFlowObject + */ +export interface OpenApiOAuthFlow { + authorizationUrl: string + tokenUrl: string + refreshUrl?: string + scopes: Dictionary +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiOAuthFlows.d.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiOAuthFlows.d.ts new file mode 100644 index 0000000000..f71f97976a --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiOAuthFlows.d.ts @@ -0,0 +1,11 @@ +import type { OpenApiOAuthFlow } from "./OpenApiOAuthFlow" + +/** + * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#oauthFlowsObject + */ +export interface OpenApiOAuthFlows { + implicit?: OpenApiOAuthFlow + password?: OpenApiOAuthFlow + clientCredentials?: OpenApiOAuthFlow + authorizationCode?: OpenApiOAuthFlow +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiOperation.d.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiOperation.d.ts new file mode 100644 index 0000000000..8394025c68 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiOperation.d.ts @@ -0,0 +1,27 @@ +import type { Dictionary } from "../../../utils/types" +import type { OpenApiCallback } from "./OpenApiCallback" +import type { OpenApiExternalDocs } from "./OpenApiExternalDocs" +import type { OpenApiParameter } from "./OpenApiParameter" +import type { OpenApiRequestBody } from "./OpenApiRequestBody" +import type { OpenApiResponses } from "./OpenApiResponses" +import type { OpenApiSecurityRequirement } from "./OpenApiSecurityRequirement" +import type { OpenApiServer } from "./OpenApiServer" + +/** + * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#operationObject + */ +export interface OpenApiOperation { + tags?: string[] + summary?: string + description?: string + externalDocs?: OpenApiExternalDocs + operationId?: string + parameters?: OpenApiParameter[] + requestBody?: OpenApiRequestBody + responses: OpenApiResponses + callbacks?: Dictionary + deprecated?: boolean + security?: OpenApiSecurityRequirement[] + servers?: OpenApiServer[] + "x-codegen"?: Record +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiParameter.d.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiParameter.d.ts new file mode 100644 index 0000000000..cebe8a50e8 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiParameter.d.ts @@ -0,0 +1,23 @@ +import type { Dictionary } from "../../../utils/types" +import type { OpenApiExample } from "./OpenApiExample" +import type { OpenApiReference } from "./OpenApiReference" +import type { OpenApiSchema } from "./OpenApiSchema" + +/** + * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#parameterObject + */ +export interface OpenApiParameter extends OpenApiReference { + name: string + in: "path" | "query" | "header" | "formData" | "cookie" + description?: string + required?: boolean + nullable?: boolean + deprecated?: boolean + allowEmptyValue?: boolean + style?: string + explode?: boolean + allowReserved?: boolean + schema?: OpenApiSchema + example?: any + examples?: Dictionary +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiPath.d.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiPath.d.ts new file mode 100644 index 0000000000..9b7cd5eaba --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiPath.d.ts @@ -0,0 +1,21 @@ +import type { OpenApiOperation } from "./OpenApiOperation" +import type { OpenApiParameter } from "./OpenApiParameter" +import type { OpenApiServer } from "./OpenApiServer" + +/** + * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#pathItemObject + */ +export interface OpenApiPath { + summary?: string + description?: string + get?: OpenApiOperation + put?: OpenApiOperation + post?: OpenApiOperation + delete?: OpenApiOperation + options?: OpenApiOperation + head?: OpenApiOperation + patch?: OpenApiOperation + trace?: OpenApiOperation + servers?: OpenApiServer[] + parameters?: OpenApiParameter[] +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiPaths.d.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiPaths.d.ts new file mode 100644 index 0000000000..0e7f38e6d6 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiPaths.d.ts @@ -0,0 +1,8 @@ +import type { OpenApiPath } from "./OpenApiPath" + +/** + * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#pathsObject + */ +export interface OpenApiPaths { + [path: string]: OpenApiPath +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiReference.d.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiReference.d.ts new file mode 100644 index 0000000000..86d809abb9 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiReference.d.ts @@ -0,0 +1,6 @@ +/** + * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#referenceObject + */ +export interface OpenApiReference { + $ref?: string +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiRequestBody.d.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiRequestBody.d.ts new file mode 100644 index 0000000000..fe48187d2b --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiRequestBody.d.ts @@ -0,0 +1,13 @@ +import type { Dictionary } from "../../../utils/types" +import type { OpenApiMediaType } from "./OpenApiMediaType" +import type { OpenApiReference } from "./OpenApiReference" + +/** + * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#requestBodyObject + */ +export interface OpenApiRequestBody extends OpenApiReference { + description?: string + content: Dictionary + required?: boolean + nullable?: boolean +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiResponse.d.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiResponse.d.ts new file mode 100644 index 0000000000..52e325c8f7 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiResponse.d.ts @@ -0,0 +1,15 @@ +import type { Dictionary } from "../../../utils/types" +import type { OpenApiHeader } from "./OpenApiHeader" +import type { OpenApiLink } from "./OpenApiLink" +import type { OpenApiMediaType } from "./OpenApiMediaType" +import type { OpenApiReference } from "./OpenApiReference" + +/** + * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#responseObject + */ +export interface OpenApiResponse extends OpenApiReference { + description: string + headers?: Dictionary + content?: Dictionary + links?: Dictionary +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiResponses.d.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiResponses.d.ts new file mode 100644 index 0000000000..d319847909 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiResponses.d.ts @@ -0,0 +1,11 @@ +import type { OpenApiReference } from "./OpenApiReference" +import type { OpenApiResponse } from "./OpenApiResponse" + +/** + * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#responsesObject + */ +export interface OpenApiResponses extends OpenApiReference { + default: OpenApiResponse + + [httpcode: string]: OpenApiResponse +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiSchema.d.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiSchema.d.ts new file mode 100644 index 0000000000..3d4f597087 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiSchema.d.ts @@ -0,0 +1,58 @@ +import type { Dictionary } from "../../../utils/types" +import type { WithEnumExtension } from "./Extensions/WithEnumExtension" +import type { OpenApiDiscriminator } from "./OpenApiDiscriminator" +import type { OpenApiExternalDocs } from "./OpenApiExternalDocs" +import type { OpenApiReference } from "./OpenApiReference" +import type { OpenApiXml } from "./OpenApiXml" + +/** + * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#schemaObject + */ +export interface OpenApiSchema extends OpenApiReference, WithEnumExtension { + title?: string + multipleOf?: number + maximum?: number + exclusiveMaximum?: boolean + minimum?: number + exclusiveMinimum?: boolean + maxLength?: number + minLength?: number + pattern?: string + maxItems?: number + minItems?: number + uniqueItems?: boolean + maxProperties?: number + minProperties?: number + required?: string[] + enum?: (string | number)[] + type?: string | string[] + allOf?: OpenApiSchema[] + oneOf?: OpenApiSchema[] + anyOf?: OpenApiSchema[] + not?: OpenApiSchema[] + items?: OpenApiSchema + properties?: Dictionary + additionalProperties?: boolean | OpenApiSchema + description?: string + format?: + | "int32" + | "int64" + | "float" + | "double" + | "string" + | "boolean" + | "byte" + | "binary" + | "date" + | "date-time" + | "password" + default?: any + nullable?: boolean + discriminator?: OpenApiDiscriminator + readOnly?: boolean + writeOnly?: boolean + xml?: OpenApiXml + externalDocs?: OpenApiExternalDocs + example?: any + deprecated?: boolean +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiSecurityRequirement.d.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiSecurityRequirement.d.ts new file mode 100644 index 0000000000..a758cf9e0e --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiSecurityRequirement.d.ts @@ -0,0 +1,6 @@ +/** + * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#securityRequirementObject + */ +export interface OpenApiSecurityRequirement { + [name: string]: string +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiSecurityScheme.d.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiSecurityScheme.d.ts new file mode 100644 index 0000000000..99077dca74 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiSecurityScheme.d.ts @@ -0,0 +1,16 @@ +import type { OpenApiOAuthFlows } from "./OpenApiOAuthFlows" +import type { OpenApiReference } from "./OpenApiReference" + +/** + * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#securitySchemeObject + */ +export interface OpenApiSecurityScheme extends OpenApiReference { + type: "apiKey" | "http" | "oauth2" | "openIdConnect" + description?: string + name?: string + in?: "query" | "header" | "cookie" + scheme?: string + bearerFormat?: string + flows?: OpenApiOAuthFlows + openIdConnectUrl?: string +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiServer.d.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiServer.d.ts new file mode 100644 index 0000000000..5c8dd73b30 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiServer.d.ts @@ -0,0 +1,11 @@ +import type { Dictionary } from "../../../utils/types" +import type { OpenApiServerVariable } from "./OpenApiServerVariable" + +/** + * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#serverObject + */ +export interface OpenApiServer { + url: string + description?: string + variables?: Dictionary +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiServerVariable.d.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiServerVariable.d.ts new file mode 100644 index 0000000000..0daee96e55 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiServerVariable.d.ts @@ -0,0 +1,10 @@ +import type { WithEnumExtension } from "./Extensions/WithEnumExtension" + +/** + * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#serverVariableObject + */ +export interface OpenApiServerVariable extends WithEnumExtension { + enum?: (string | number)[] + default: string + description?: string +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiTag.d.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiTag.d.ts new file mode 100644 index 0000000000..e99dd6f7d8 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiTag.d.ts @@ -0,0 +1,10 @@ +import type { OpenApiExternalDocs } from "./OpenApiExternalDocs" + +/** + * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#tagObject + */ +export interface OpenApiTag { + name: string + description?: string + externalDocs?: OpenApiExternalDocs +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiXml.d.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiXml.d.ts new file mode 100644 index 0000000000..2e6ab8f8f2 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiXml.d.ts @@ -0,0 +1,10 @@ +/** + * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#xmlObject + */ +export interface OpenApiXml { + name?: string + namespace?: string + prefix?: string + attribute?: boolean + wrapped?: boolean +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/escapeName.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/escapeName.ts new file mode 100644 index 0000000000..0de7ab211b --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/escapeName.ts @@ -0,0 +1,9 @@ +export const escapeName = (value: string): string => { + if (value || value === "") { + const validName = /^[a-zA-Z_$][\w$]+$/g.test(value) + if (!validName) { + return `'${value}'` + } + } + return value +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/extendEnum.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/extendEnum.ts new file mode 100644 index 0000000000..5f9c46da9c --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/extendEnum.ts @@ -0,0 +1,24 @@ +import type { Enum } from "../../../client/interfaces/Enum" +import { isString } from "../../../utils/isString" +import type { WithEnumExtension } from "../interfaces/Extensions/WithEnumExtension" + +/** + * Extend the enum with the x-enum properties. This adds the capability + * to use names and descriptions inside the generated enums. + * @param enumerators + * @param definition + */ +export const extendEnum = ( + enumerators: Enum[], + definition: WithEnumExtension +): Enum[] => { + const names = definition["x-enum-varnames"]?.filter(isString) + const descriptions = definition["x-enum-descriptions"]?.filter(isString) + + return enumerators.map((enumerator, index) => ({ + name: names?.[index] || enumerator.name, + description: descriptions?.[index] || enumerator.description, + value: enumerator.value, + type: enumerator.type, + })) +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getContent.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getContent.ts new file mode 100644 index 0000000000..83869c28c0 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getContent.ts @@ -0,0 +1,51 @@ +import { isDefined } from "../../../utils/isDefined" +import type { Dictionary } from "../../../utils/types" +import type { OpenApi } from "../interfaces/OpenApi" +import type { OpenApiMediaType } from "../interfaces/OpenApiMediaType" +import type { OpenApiSchema } from "../interfaces/OpenApiSchema" + +export interface Content { + mediaType: string + schema: OpenApiSchema +} + +const BASIC_MEDIA_TYPES = [ + "application/json-patch+json", + "application/json", + "application/x-www-form-urlencoded", + "text/json", + "text/plain", + "multipart/form-data", + "multipart/mixed", + "multipart/related", + "multipart/batch", +] + +export const getContent = ( + openApi: OpenApi, + content: Dictionary +): Content | null => { + const basicMediaTypeWithSchema = Object.keys(content) + .filter((mediaType) => { + const cleanMediaType = mediaType.split(";")[0].trim() + return BASIC_MEDIA_TYPES.includes(cleanMediaType) + }) + .find((mediaType) => isDefined(content[mediaType]?.schema)) + if (basicMediaTypeWithSchema) { + return { + mediaType: basicMediaTypeWithSchema, + schema: content[basicMediaTypeWithSchema].schema as OpenApiSchema, + } + } + + const firstMediaTypeWithSchema = Object.keys(content).find((mediaType) => + isDefined(content[mediaType]?.schema) + ) + if (firstMediaTypeWithSchema) { + return { + mediaType: firstMediaTypeWithSchema, + schema: content[firstMediaTypeWithSchema].schema as OpenApiSchema, + } + } + return null +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getEnum.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getEnum.ts new file mode 100644 index 0000000000..7c7942a5d6 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getEnum.ts @@ -0,0 +1,34 @@ +import type { Enum } from "../../../client/interfaces/Enum" + +export const getEnum = (values?: (string | number)[]): Enum[] => { + if (Array.isArray(values)) { + return values + .filter((value, index, arr) => { + return arr.indexOf(value) === index + }) + .filter((value: any) => { + return typeof value === "number" || typeof value === "string" + }) + .map((value) => { + if (typeof value === "number") { + return { + name: `'_${value}'`, + value: String(value), + type: "number", + description: null, + } + } + return { + name: String(value) + .replace(/\W+/g, "_") + .replace(/^(\d+)/g, "_$1") + .replace(/([a-z])([A-Z]+)/g, "$1_$2") + .toUpperCase(), + value: `'${value.replace(/'/g, "\\'")}'`, + type: "string", + description: null, + } + }) + } + return [] +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getMappedType.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getMappedType.ts new file mode 100644 index 0000000000..38f5cf5fab --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getMappedType.ts @@ -0,0 +1,35 @@ +const TYPE_MAPPINGS = new Map([ + ["file", "binary"], + ["any", "any"], + ["object", "any"], + ["array", "any[]"], + ["boolean", "boolean"], + ["byte", "number"], + ["int", "number"], + ["integer", "number"], + ["float", "number"], + ["double", "number"], + ["short", "number"], + ["long", "number"], + ["number", "number"], + ["char", "string"], + ["date", "string"], + ["date-time", "string"], + ["password", "string"], + ["string", "string"], + ["void", "void"], + ["null", "null"], +]) + +/** + * Get mapped type for given type to any basic Typescript/Javascript type. + */ +export const getMappedType = ( + type: string, + format?: string +): string | undefined => { + if (format === "binary") { + return "binary" + } + return TYPE_MAPPINGS.get(type) +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getModel.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getModel.ts new file mode 100644 index 0000000000..88867dd38a --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getModel.ts @@ -0,0 +1,224 @@ +import type { Model } from "../../../client/interfaces/Model" +import { getPattern } from "../../../utils/getPattern" +import type { OpenApi } from "../interfaces/OpenApi" +import type { OpenApiSchema } from "../interfaces/OpenApiSchema" +import { extendEnum } from "./extendEnum" +import { getEnum } from "./getEnum" +import { getModelComposition } from "./getModelComposition" +import { getModelDefault } from "./getModelDefault" +import { getModelProperties } from "./getModelProperties" +import { getType } from "./getType" + +export const getModel = ( + openApi: OpenApi, + definition: OpenApiSchema, + isDefinition: boolean = false, + name: string = "" +): Model => { + const model: Model = { + spec: definition, + name, + export: "interface", + type: "any", + base: "any", + template: null, + link: null, + description: definition.description || null, + deprecated: definition.deprecated === true, + isDefinition, + isReadOnly: definition.readOnly === true, + isNullable: definition.nullable === true, + isRequired: false, + format: definition.format, + maximum: definition.maximum, + exclusiveMaximum: definition.exclusiveMaximum, + minimum: definition.minimum, + exclusiveMinimum: definition.exclusiveMinimum, + multipleOf: definition.multipleOf, + maxLength: definition.maxLength, + minLength: definition.minLength, + maxItems: definition.maxItems, + minItems: definition.minItems, + uniqueItems: definition.uniqueItems, + maxProperties: definition.maxProperties, + minProperties: definition.minProperties, + pattern: getPattern(definition.pattern), + imports: [], + enum: [], + enums: [], + properties: [], + } + + if (definition.$ref) { + const definitionRef = getType(definition.$ref) + model.export = "reference" + model.type = definitionRef.type + model.base = definitionRef.base + model.template = definitionRef.template + model.imports.push(...definitionRef.imports) + model.default = getModelDefault(definition, model) + return model + } + + if (definition.enum && definition.type !== "boolean") { + const enumerators = getEnum(definition.enum) + const extendedEnumerators = extendEnum(enumerators, definition) + if (extendedEnumerators.length) { + model.export = "enum" + model.type = "string" + model.base = "string" + model.enum.push(...extendedEnumerators) + model.default = getModelDefault(definition, model) + return model + } + } + + if (definition.type === "array" && definition.items) { + if (definition.items.$ref) { + const arrayItems = getType(definition.items.$ref) + model.export = "array" + model.type = arrayItems.type + model.base = arrayItems.base + model.template = arrayItems.template + model.imports.push(...arrayItems.imports) + model.default = getModelDefault(definition, model) + return model + } else { + const arrayItems = getModel(openApi, definition.items) + model.export = "array" + model.type = arrayItems.type + model.base = arrayItems.base + model.template = arrayItems.template + model.link = arrayItems + model.imports.push(...arrayItems.imports) + model.default = getModelDefault(definition, model) + return model + } + } + + if ( + definition.type === "object" && + (typeof definition.additionalProperties === "object" || + definition.additionalProperties === true) + ) { + const ap = + typeof definition.additionalProperties === "object" + ? definition.additionalProperties + : {} + if (ap.$ref) { + const additionalProperties = getType(ap.$ref) + model.export = "dictionary" + model.type = additionalProperties.type + model.base = additionalProperties.base + model.template = additionalProperties.template + model.imports.push(...additionalProperties.imports) + model.default = getModelDefault(definition, model) + return model + } else { + const additionalProperties = getModel(openApi, ap) + model.export = "dictionary" + model.type = additionalProperties.type + model.base = additionalProperties.base + model.template = additionalProperties.template + model.link = additionalProperties + model.imports.push(...additionalProperties.imports) + model.default = getModelDefault(definition, model) + return model + } + } + + if (definition.oneOf?.length) { + const composition = getModelComposition( + openApi, + definition, + definition.oneOf, + "one-of", + getModel + ) + model.export = composition.type + model.imports.push(...composition.imports) + model.properties.push(...composition.properties) + model.enums.push(...composition.enums) + return model + } + + if (definition.anyOf?.length) { + const composition = getModelComposition( + openApi, + definition, + definition.anyOf, + "any-of", + getModel + ) + model.export = composition.type + model.imports.push(...composition.imports) + model.properties.push(...composition.properties) + model.enums.push(...composition.enums) + return model + } + + if (definition.allOf?.length) { + const composition = getModelComposition( + openApi, + definition, + definition.allOf, + "all-of", + getModel + ) + model.export = composition.type + model.imports.push(...composition.imports) + model.properties.push(...composition.properties) + model.enums.push(...composition.enums) + return model + } + + if (definition.type === "object") { + if (definition.properties) { + model.export = "interface" + model.type = "any" + model.base = "any" + model.default = getModelDefault(definition, model) + + const modelProperties = getModelProperties( + openApi, + definition, + getModel, + model + ) + modelProperties.forEach((modelProperty) => { + model.imports.push(...modelProperty.imports) + model.enums.push(...modelProperty.enums) + model.properties.push(modelProperty) + if (modelProperty.export === "enum") { + model.enums.push(modelProperty) + } + }) + return model + } else { + const additionalProperties = getModel(openApi, {}) + model.export = "dictionary" + model.type = additionalProperties.type + model.base = additionalProperties.base + model.template = additionalProperties.template + model.link = additionalProperties + model.imports.push(...additionalProperties.imports) + model.default = getModelDefault(definition, model) + return model + } + } + + // If the schema has a type than it can be a basic or generic type. + if (definition.type) { + const definitionType = getType(definition.type, definition.format) + model.export = "generic" + model.type = definitionType.type + model.base = definitionType.base + model.template = definitionType.template + model.isNullable = definitionType.isNullable || model.isNullable + model.imports.push(...definitionType.imports) + model.default = getModelDefault(definition, model) + return model + } + + return model +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getModelComposition.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getModelComposition.ts new file mode 100644 index 0000000000..2ff0f9cd4f --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getModelComposition.ts @@ -0,0 +1,92 @@ +import type { Model } from "../../../client/interfaces/Model" +import type { ModelComposition } from "../../../client/interfaces/ModelComposition" +import type { OpenApi } from "../interfaces/OpenApi" +import type { OpenApiSchema } from "../interfaces/OpenApiSchema" +import type { getModel } from "./getModel" +import { getModelProperties } from "./getModelProperties" +import { getRequiredPropertiesFromComposition } from "./getRequiredPropertiesFromComposition" + +// Fix for circular dependency +export type GetModelFn = typeof getModel + +export const getModelComposition = ( + openApi: OpenApi, + definition: OpenApiSchema, + definitions: OpenApiSchema[], + type: "one-of" | "any-of" | "all-of", + getModel: GetModelFn +): ModelComposition => { + const composition: ModelComposition = { + type, + imports: [], + enums: [], + properties: [], + } + + const properties: Model[] = [] + + definitions + .map((definition) => getModel(openApi, definition)) + .filter((model) => { + const hasProperties = model.properties.length + const hasEnums = model.enums.length + const isObject = model.type === "any" + const isDictionary = model.export === "dictionary" + const isEmpty = isObject && !hasProperties && !hasEnums + return !isEmpty || isDictionary + }) + .forEach((model) => { + composition.imports.push(...model.imports) + composition.enums.push(...model.enums) + composition.properties.push(model) + }) + + if (definition.required) { + const requiredProperties = getRequiredPropertiesFromComposition( + openApi, + definition.required, + definitions, + getModel + ) + requiredProperties.forEach((requiredProperty) => { + composition.imports.push(...requiredProperty.imports) + composition.enums.push(...requiredProperty.enums) + }) + properties.push(...requiredProperties) + } + + if (definition.properties) { + const modelProperties = getModelProperties(openApi, definition, getModel) + modelProperties.forEach((modelProperty) => { + composition.imports.push(...modelProperty.imports) + composition.enums.push(...modelProperty.enums) + if (modelProperty.export === "enum") { + composition.enums.push(modelProperty) + } + }) + properties.push(...modelProperties) + } + + if (properties.length) { + composition.properties.push({ + spec: definition, + name: "properties", + export: "interface", + type: "any", + base: "any", + template: null, + link: null, + description: "", + isDefinition: false, + isReadOnly: false, + isNullable: false, + isRequired: false, + imports: [], + enum: [], + enums: [], + properties, + }) + } + + return composition +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getModelDefault.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getModelDefault.ts new file mode 100644 index 0000000000..55e1b79c8b --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getModelDefault.ts @@ -0,0 +1,42 @@ +import type { Model } from "../../../client/interfaces/Model" +import type { OpenApiSchema } from "../interfaces/OpenApiSchema" + +export const getModelDefault = ( + definition: OpenApiSchema, + model?: Model +): string | undefined => { + if (definition.default === undefined) { + return undefined + } + + if (definition.default === null) { + return "null" + } + + const type = definition.type || typeof definition.default + + switch (type) { + case "int": + case "integer": + case "number": + if (model?.export === "enum" && model.enum?.[definition.default]) { + return model.enum[definition.default].value + } + return definition.default + + case "boolean": + return JSON.stringify(definition.default) + + case "string": + return `'${definition.default}'` + + case "object": + try { + return JSON.stringify(definition.default, null, 4) + } catch (e) { + // Ignore + } + } + + return undefined +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getModelProperties.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getModelProperties.ts new file mode 100644 index 0000000000..696eb32a19 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getModelProperties.ts @@ -0,0 +1,112 @@ +import type { Model } from "../../../client/interfaces/Model" +import { + findOneOfParentDiscriminator, + mapPropertyValue, +} from "../../../utils/discriminator" +import { getPattern } from "../../../utils/getPattern" +import type { OpenApi } from "../interfaces/OpenApi" +import type { OpenApiSchema } from "../interfaces/OpenApiSchema" +import { escapeName } from "./escapeName" +import type { getModel } from "./getModel" +import { getType } from "./getType" + +// Fix for circular dependency +export type GetModelFn = typeof getModel + +export const getModelProperties = ( + openApi: OpenApi, + definition: OpenApiSchema, + getModel: GetModelFn, + parent?: Model +): Model[] => { + const models: Model[] = [] + const discriminator = findOneOfParentDiscriminator(openApi, parent) + for (const propertyName in definition.properties) { + if (definition.properties.hasOwnProperty(propertyName)) { + const property = definition.properties[propertyName] + const propertyRequired = !!definition.required?.includes(propertyName) + const propertyValues: Omit< + Model, + | "export" + | "type" + | "base" + | "template" + | "link" + | "isNullable" + | "imports" + | "enum" + | "enums" + | "properties" + > = { + spec: definition, + name: escapeName(propertyName), + description: property.description || null, + deprecated: property.deprecated === true, + isDefinition: false, + isReadOnly: property.readOnly === true, + isRequired: propertyRequired, + format: property.format, + maximum: property.maximum, + exclusiveMaximum: property.exclusiveMaximum, + minimum: property.minimum, + exclusiveMinimum: property.exclusiveMinimum, + multipleOf: property.multipleOf, + maxLength: property.maxLength, + minLength: property.minLength, + maxItems: property.maxItems, + minItems: property.minItems, + uniqueItems: property.uniqueItems, + maxProperties: property.maxProperties, + minProperties: property.minProperties, + pattern: getPattern(property.pattern), + } + if (parent && discriminator?.propertyName == propertyName) { + models.push({ + export: "reference", + type: "string", + base: `'${mapPropertyValue(discriminator, parent)}'`, + template: null, + isNullable: property.nullable === true, + link: null, + imports: [], + enum: [], + enums: [], + properties: [], + ...propertyValues, + }) + } else if (property.$ref) { + const model = getType(property.$ref) + models.push({ + export: "reference", + type: model.type, + base: model.base, + template: model.template, + link: null, + isNullable: model.isNullable || property.nullable === true, + imports: model.imports, + enum: [], + enums: [], + properties: [], + ...propertyValues, + }) + } else { + const model = getModel(openApi, property) + models.push({ + export: model.export, + type: model.type, + base: model.base, + template: model.template, + link: model.link, + isNullable: model.isNullable || property.nullable === true, + imports: model.imports, + enum: model.enum, + enums: model.enums, + properties: model.properties, + ...propertyValues, + }) + } + } + } + + return models +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getModelTemplate.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getModelTemplate.ts new file mode 100644 index 0000000000..85a5f5b8ce --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getModelTemplate.ts @@ -0,0 +1,11 @@ +import type { Type } from "../../../client/interfaces/Type" + +/** + * If our model has a template type, then we want to generalize that! + * In that case we should return "" as our template type. + * @param modelClass The parsed model class type. + * @returns The model template type ( or empty). + */ +export const getModelTemplate = (modelClass: Type): string => { + return modelClass.template ? "" : "" +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getModels.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getModels.ts new file mode 100644 index 0000000000..777da97586 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getModels.ts @@ -0,0 +1,112 @@ +import type { Model } from "../../../client/interfaces/Model" +import type { OpenApi } from "../interfaces/OpenApi" +import { getModel } from "./getModel" +import { reservedWords } from "./getOperationParameterName" +import { getType } from "./getType" +import { OperationParameter } from "../../../client/interfaces/OperationParameter" +import { OpenApiSchema } from "../interfaces/OpenApiSchema" +import { Dictionary } from "../../../utils/types" +import { OpenApiParameter } from "../interfaces/OpenApiParameter" +import { listOperations } from "./listOperations" + +export const getModels = (openApi: OpenApi): Model[] => { + const models: Model[] = [] + if (openApi.components) { + for (const definitionName in openApi.components.schemas) { + if (openApi.components.schemas.hasOwnProperty(definitionName)) { + const definition = openApi.components.schemas[definitionName] + const definitionType = getType(definitionName) + const model = getModel( + openApi, + definition, + true, + definitionType.base.replace(reservedWords, "_$1") + ) + models.push(model) + } + } + } + + /** + * Bundle all query parameters in a single typed object + * when x-codegen.queryParams is declared on the operation. + */ + const operations = listOperations(openApi) + for (const operation of operations) { + if (operation.codegen.queryParams) { + const definition = getDefinitionFromParametersQuery( + operation.parametersQuery + ) + const model = getModel( + openApi, + definition, + true, + operation.codegen.queryParams + ) + models.push(model) + } + } + + return models +} + +/** + * Combines and converts query parameters into schema properties. + * Given a typical parameter OAS: + * ``` + * parameters: + * - in: query + * name: limit + * schema: + * type: integer + * default: 10 + * required: true + * description: Limit the number of results returned. + * ``` + * Convert into a schema property: + * ``` + * UnnamedSchema: + * type: object + * required: + * - limit + * properties: + * limit: + * description: Limit the number of results returned. + * type: integer + * default: 10 + * ``` + */ +const getDefinitionFromParametersQuery = ( + parametersQuery: OperationParameter[] +): OpenApiSchema => { + /** + * Identify query parameters that are required (non-optional). + */ + const required = parametersQuery + .filter((parameter) => (parameter.spec as OpenApiParameter).required) + .map((parameter) => (parameter.spec as OpenApiParameter).name) + + const properties: Dictionary = {} + for (const parameter of parametersQuery) { + const spec = parameter.spec as OpenApiParameter + /** + * Augment a copy of schema with description and deprecated + * and assign it as a named property on the schema. + */ + properties[spec.name] = Object.assign( + { ...spec.schema }, + { + description: spec.description, + deprecated: spec.deprecated, + } + ) + } + /** + * Return an unnamed schema definition. + */ + return { + type: "object", + required, + properties, + } +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getOperation.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getOperation.ts new file mode 100644 index 0000000000..42abec5dfc --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getOperation.ts @@ -0,0 +1,95 @@ +import type { Operation } from "../../../client/interfaces/Operation" +import type { OperationParameters } from "../../../client/interfaces/OperationParameters" +import type { OperationCodegen } from "../../../client/interfaces/OperationCodegen" +import type { OpenApi } from "../interfaces/OpenApi" +import type { OpenApiOperation } from "../interfaces/OpenApiOperation" +import type { OpenApiRequestBody } from "../interfaces/OpenApiRequestBody" +import { getOperationErrors } from "./getOperationErrors" +import { getOperationName } from "./getOperationName" +import { getOperationParameters } from "./getOperationParameters" +import { getOperationRequestBody } from "./getOperationRequestBody" +import { getOperationResponseHeader } from "./getOperationResponseHeader" +import { getOperationResponses } from "./getOperationResponses" +import { getOperationResults } from "./getOperationResults" +import { getRef } from "./getRef" +import { getServiceName } from "./getServiceName" +import { sortByRequired } from "./sortByRequired" + +export const getOperation = ( + openApi: OpenApi, + url: string, + method: string, + tag: string, + op: OpenApiOperation, + pathParams: OperationParameters +): Operation => { + const serviceName = getServiceName(tag) + const operationName = getOperationName(url, method, op.operationId) + const codegen: OperationCodegen = op["x-codegen"] || {} + + // Create a new operation object for this method. + const operation: Operation = { + service: serviceName, + name: codegen.method || operationName, + operationId: op.operationId || null, + summary: op.summary || null, + description: op.description || null, + deprecated: op.deprecated === true, + method: method.toUpperCase(), + path: url, + parameters: [...pathParams.parameters], + parametersPath: [...pathParams.parametersPath], + parametersQuery: [...pathParams.parametersQuery], + parametersForm: [...pathParams.parametersForm], + parametersHeader: [...pathParams.parametersHeader], + parametersCookie: [...pathParams.parametersCookie], + parametersBody: pathParams.parametersBody, + imports: [], + errors: [], + results: [], + responseHeader: null, + codegen, + } + + // Parse the operation parameters (path, query, body, etc). + if (op.parameters) { + const parameters = getOperationParameters(openApi, op.parameters) + operation.imports.push(...parameters.imports) + operation.parameters.push(...parameters.parameters) + operation.parametersPath.push(...parameters.parametersPath) + operation.parametersQuery.push(...parameters.parametersQuery) + operation.parametersForm.push(...parameters.parametersForm) + operation.parametersHeader.push(...parameters.parametersHeader) + operation.parametersCookie.push(...parameters.parametersCookie) + operation.parametersBody = parameters.parametersBody + } + + if (op.requestBody) { + const requestBodyDef = getRef(openApi, op.requestBody) + const requestBody = getOperationRequestBody(openApi, requestBodyDef) + operation.imports.push(...requestBody.imports) + operation.parameters.push(requestBody) + operation.parametersBody = requestBody + } + + // Parse the operation responses. + if (op.responses) { + const operationResponses = getOperationResponses(openApi, op.responses) + const operationResults = getOperationResults(operationResponses) + operation.errors = getOperationErrors(operationResponses) + operation.responseHeader = getOperationResponseHeader(operationResults) + + operationResults.forEach((operationResult) => { + operation.results.push(operationResult) + operation.imports.push(...operationResult.imports) + }) + } + + if (codegen.queryParams) { + operation.imports.push(codegen.queryParams) + } + + operation.parameters = operation.parameters.sort(sortByRequired) + + return operation +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getOperationErrors.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getOperationErrors.ts new file mode 100644 index 0000000000..c877333e2f --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getOperationErrors.ts @@ -0,0 +1,15 @@ +import type { OperationError } from "../../../client/interfaces/OperationError" +import type { OperationResponse } from "../../../client/interfaces/OperationResponse" + +export const getOperationErrors = ( + operationResponses: OperationResponse[] +): OperationError[] => { + return operationResponses + .filter((operationResponse) => { + return operationResponse.code >= 300 && operationResponse.description + }) + .map((response) => ({ + code: response.code, + description: response.description!, + })) +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getOperationName.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getOperationName.ts new file mode 100644 index 0000000000..742997f7f9 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getOperationName.ts @@ -0,0 +1,28 @@ +import camelCase from "camelcase" + +/** + * Convert the input value to a correct operation (method) classname. + * This will use the operation ID - if available - and otherwise fallback + * on a generated name from the URL + */ +export const getOperationName = ( + url: string, + method: string, + operationId?: string +): string => { + if (operationId) { + return camelCase( + operationId + .replace(/^[^a-zA-Z]+/g, "") + .replace(/[^\w\-]+/g, "-") + .trim() + ) + } + + const urlWithoutPlaceholders = url + .replace(/[^/]*?{api-version}.*?\//g, "") + .replace(/{(.*?)}/g, "") + .replace(/\//g, "-") + + return camelCase(`${method}-${urlWithoutPlaceholders}`) +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getOperationParameter.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getOperationParameter.ts new file mode 100644 index 0000000000..c04a3d6ae2 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getOperationParameter.ts @@ -0,0 +1,99 @@ +import type { OperationParameter } from "../../../client/interfaces/OperationParameter" +import { getPattern } from "../../../utils/getPattern" +import type { OpenApi } from "../interfaces/OpenApi" +import type { OpenApiParameter } from "../interfaces/OpenApiParameter" +import type { OpenApiSchema } from "../interfaces/OpenApiSchema" +import { getModel } from "./getModel" +import { getModelDefault } from "./getModelDefault" +import { getOperationParameterName } from "./getOperationParameterName" +import { getRef } from "./getRef" +import { getType } from "./getType" + +export const getOperationParameter = ( + openApi: OpenApi, + parameter: OpenApiParameter +): OperationParameter => { + const operationParameter: OperationParameter = { + spec: parameter, + in: parameter.in, + prop: parameter.name, + export: "interface", + name: getOperationParameterName(parameter.name), + type: "any", + base: "any", + template: null, + link: null, + description: parameter.description || null, + deprecated: parameter.deprecated === true, + isDefinition: false, + isReadOnly: false, + isRequired: parameter.required === true, + isNullable: parameter.nullable === true, + imports: [], + enum: [], + enums: [], + properties: [], + mediaType: null, + } + + if (parameter.$ref) { + const definitionRef = getType(parameter.$ref) + operationParameter.export = "reference" + operationParameter.type = definitionRef.type + operationParameter.base = definitionRef.base + operationParameter.template = definitionRef.template + operationParameter.imports.push(...definitionRef.imports) + return operationParameter + } + + let schema = parameter.schema + if (schema) { + if (schema.$ref?.startsWith("#/components/parameters/")) { + schema = getRef(openApi, schema) + } + if (schema.$ref) { + const model = getType(schema.$ref) + operationParameter.export = "reference" + operationParameter.type = model.type + operationParameter.base = model.base + operationParameter.template = model.template + operationParameter.imports.push(...model.imports) + operationParameter.default = getModelDefault(schema) + return operationParameter + } else { + const model = getModel(openApi, schema) + operationParameter.export = model.export + operationParameter.type = model.type + operationParameter.base = model.base + operationParameter.template = model.template + operationParameter.link = model.link + operationParameter.isReadOnly = model.isReadOnly + operationParameter.isRequired = + operationParameter.isRequired || model.isRequired + operationParameter.isNullable = + operationParameter.isNullable || model.isNullable + operationParameter.format = model.format + operationParameter.maximum = model.maximum + operationParameter.exclusiveMaximum = model.exclusiveMaximum + operationParameter.minimum = model.minimum + operationParameter.exclusiveMinimum = model.exclusiveMinimum + operationParameter.multipleOf = model.multipleOf + operationParameter.maxLength = model.maxLength + operationParameter.minLength = model.minLength + operationParameter.maxItems = model.maxItems + operationParameter.minItems = model.minItems + operationParameter.uniqueItems = model.uniqueItems + operationParameter.maxProperties = model.maxProperties + operationParameter.minProperties = model.minProperties + operationParameter.pattern = getPattern(model.pattern) + operationParameter.default = model.default + operationParameter.imports.push(...model.imports) + operationParameter.enum.push(...model.enum) + operationParameter.enums.push(...model.enums) + operationParameter.properties.push(...model.properties) + return operationParameter + } + } + + return operationParameter +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getOperationParameterName.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getOperationParameterName.ts new file mode 100644 index 0000000000..f550547154 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getOperationParameterName.ts @@ -0,0 +1,16 @@ +import camelCase from "camelcase" + +export const reservedWords = + /^(arguments|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|eval|export|extends|false|finally|for|function|if|implements|import|in|instanceof|interface|let|new|null|package|private|protected|public|return|static|super|switch|this|throw|true|try|typeof|var|void|while|with|yield)$/g + +/** + * Replaces any invalid characters from a parameter name. + * For example: 'filter.someProperty' becomes 'filterSomeProperty'. + */ +export const getOperationParameterName = (value: string): string => { + const clean = value + .replace(/^[^a-zA-Z]+/g, "") + .replace(/[^\w\-]+/g, "-") + .trim() + return camelCase(clean).replace(reservedWords, "_$1") +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getOperationParameters.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getOperationParameters.ts new file mode 100644 index 0000000000..e9dc91ccba --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getOperationParameters.ts @@ -0,0 +1,64 @@ +import type { OperationParameters } from "../../../client/interfaces/OperationParameters" +import type { OpenApi } from "../interfaces/OpenApi" +import type { OpenApiParameter } from "../interfaces/OpenApiParameter" +import { getOperationParameter } from "./getOperationParameter" +import { getRef } from "./getRef" + +export const getOperationParameters = ( + openApi: OpenApi, + parameters: OpenApiParameter[] +): OperationParameters => { + const operationParameters: OperationParameters = { + imports: [], + parameters: [], + parametersPath: [], + parametersQuery: [], + parametersForm: [], + parametersCookie: [], + parametersHeader: [], + parametersBody: null, // Not used in V3 -> @see requestBody + } + + // Iterate over the parameters + parameters.forEach((parameterOrReference) => { + const parameterDef = getRef(openApi, parameterOrReference) + const parameter = getOperationParameter(openApi, parameterDef) + + // We ignore the "api-version" param, since we do not want to add this + // as the first / default parameter for each of the service calls. + if (parameter.prop !== "api-version") { + switch (parameterDef.in) { + case "path": + operationParameters.parametersPath.push(parameter) + operationParameters.parameters.push(parameter) + operationParameters.imports.push(...parameter.imports) + break + + case "query": + operationParameters.parametersQuery.push(parameter) + operationParameters.parameters.push(parameter) + operationParameters.imports.push(...parameter.imports) + break + + case "formData": + operationParameters.parametersForm.push(parameter) + operationParameters.parameters.push(parameter) + operationParameters.imports.push(...parameter.imports) + break + + case "cookie": + operationParameters.parametersCookie.push(parameter) + operationParameters.parameters.push(parameter) + operationParameters.imports.push(...parameter.imports) + break + + case "header": + operationParameters.parametersHeader.push(parameter) + operationParameters.parameters.push(parameter) + operationParameters.imports.push(...parameter.imports) + break + } + } + }) + return operationParameters +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getOperationRequestBody.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getOperationRequestBody.ts new file mode 100644 index 0000000000..80dd9ee5f6 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getOperationRequestBody.ts @@ -0,0 +1,90 @@ +import type { OperationParameter } from "../../../client/interfaces/OperationParameter" +import { getPattern } from "../../../utils/getPattern" +import type { OpenApi } from "../interfaces/OpenApi" +import type { OpenApiRequestBody } from "../interfaces/OpenApiRequestBody" +import { getContent } from "./getContent" +import { getModel } from "./getModel" +import { getType } from "./getType" + +export const getOperationRequestBody = ( + openApi: OpenApi, + body: OpenApiRequestBody +): OperationParameter => { + const requestBody: OperationParameter = { + spec: body, + in: "body", + export: "interface", + prop: "requestBody", + name: "requestBody", + type: "any", + base: "any", + template: null, + link: null, + description: body.description || null, + default: undefined, + isDefinition: false, + isReadOnly: false, + isRequired: body.required === true, + isNullable: body.nullable === true, + imports: [], + enum: [], + enums: [], + properties: [], + mediaType: null, + } + + if (body.content) { + const content = getContent(openApi, body.content) + if (content) { + requestBody.mediaType = content.mediaType + switch (requestBody.mediaType) { + case "application/x-www-form-urlencoded": + case "multipart/form-data": + requestBody.in = "formData" + requestBody.name = "formData" + requestBody.prop = "formData" + break + } + if (content.schema.$ref) { + const model = getType(content.schema.$ref) + requestBody.export = "reference" + requestBody.type = model.type + requestBody.base = model.base + requestBody.template = model.template + requestBody.imports.push(...model.imports) + return requestBody + } else { + const model = getModel(openApi, content.schema) + requestBody.export = model.export + requestBody.type = model.type + requestBody.base = model.base + requestBody.template = model.template + requestBody.link = model.link + requestBody.isReadOnly = model.isReadOnly + requestBody.isRequired = requestBody.isRequired || model.isRequired + requestBody.isNullable = requestBody.isNullable || model.isNullable + requestBody.format = model.format + requestBody.maximum = model.maximum + requestBody.exclusiveMaximum = model.exclusiveMaximum + requestBody.minimum = model.minimum + requestBody.exclusiveMinimum = model.exclusiveMinimum + requestBody.multipleOf = model.multipleOf + requestBody.maxLength = model.maxLength + requestBody.minLength = model.minLength + requestBody.maxItems = model.maxItems + requestBody.minItems = model.minItems + requestBody.uniqueItems = model.uniqueItems + requestBody.maxProperties = model.maxProperties + requestBody.minProperties = model.minProperties + requestBody.pattern = getPattern(model.pattern) + requestBody.imports.push(...model.imports) + requestBody.enum.push(...model.enum) + requestBody.enums.push(...model.enums) + requestBody.properties.push(...model.properties) + return requestBody + } + } + } + + return requestBody +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getOperationResponse.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getOperationResponse.ts new file mode 100644 index 0000000000..5d84a1716d --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getOperationResponse.ts @@ -0,0 +1,99 @@ +import type { OperationResponse } from "../../../client/interfaces/OperationResponse" +import { getPattern } from "../../../utils/getPattern" +import type { OpenApi } from "../interfaces/OpenApi" +import type { OpenApiResponse } from "../interfaces/OpenApiResponse" +import type { OpenApiSchema } from "../interfaces/OpenApiSchema" +import { getContent } from "./getContent" +import { getModel } from "./getModel" +import { getRef } from "./getRef" +import { getType } from "./getType" + +export const getOperationResponse = ( + openApi: OpenApi, + response: OpenApiResponse, + responseCode: number +): OperationResponse => { + const operationResponse: OperationResponse = { + spec: response, + in: "response", + name: "", + code: responseCode, + description: response.description || null, + export: "generic", + type: "any", + base: "any", + template: null, + link: null, + isDefinition: false, + isReadOnly: false, + isRequired: false, + isNullable: false, + imports: [], + enum: [], + enums: [], + properties: [], + } + + if (response.content) { + const content = getContent(openApi, response.content) + if (content) { + if (content.schema.$ref?.startsWith("#/components/responses/")) { + content.schema = getRef(openApi, content.schema) + } + if (content.schema.$ref) { + const model = getType(content.schema.$ref) + operationResponse.export = "reference" + operationResponse.type = model.type + operationResponse.base = model.base + operationResponse.template = model.template + operationResponse.imports.push(...model.imports) + return operationResponse + } else { + const model = getModel(openApi, content.schema) + operationResponse.export = model.export + operationResponse.type = model.type + operationResponse.base = model.base + operationResponse.template = model.template + operationResponse.link = model.link + operationResponse.isReadOnly = model.isReadOnly + operationResponse.isRequired = model.isRequired + operationResponse.isNullable = model.isNullable + operationResponse.format = model.format + operationResponse.maximum = model.maximum + operationResponse.exclusiveMaximum = model.exclusiveMaximum + operationResponse.minimum = model.minimum + operationResponse.exclusiveMinimum = model.exclusiveMinimum + operationResponse.multipleOf = model.multipleOf + operationResponse.maxLength = model.maxLength + operationResponse.minLength = model.minLength + operationResponse.maxItems = model.maxItems + operationResponse.minItems = model.minItems + operationResponse.uniqueItems = model.uniqueItems + operationResponse.maxProperties = model.maxProperties + operationResponse.minProperties = model.minProperties + operationResponse.pattern = getPattern(model.pattern) + operationResponse.imports.push(...model.imports) + operationResponse.enum.push(...model.enum) + operationResponse.enums.push(...model.enums) + operationResponse.properties.push(...model.properties) + return operationResponse + } + } + } + + // We support basic properties from response headers, since both + // fetch and XHR client just support string types. + if (response.headers) { + for (const name in response.headers) { + if (response.headers.hasOwnProperty(name)) { + operationResponse.in = "header" + operationResponse.name = name + operationResponse.type = "string" + operationResponse.base = "string" + return operationResponse + } + } + } + + return operationResponse +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getOperationResponseCode.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getOperationResponseCode.ts new file mode 100644 index 0000000000..09d8ef3369 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getOperationResponseCode.ts @@ -0,0 +1,18 @@ +export const getOperationResponseCode = ( + value: string | "default" +): number | null => { + // You can specify a "default" response, this is treated as HTTP code 200 + if (value === "default") { + return 200 + } + + // Check if we can parse the code and return of successful. + if (/[0-9]+/g.test(value)) { + const code = parseInt(value) + if (Number.isInteger(code)) { + return Math.abs(code) + } + } + + return null +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getOperationResponseHeader.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getOperationResponseHeader.ts new file mode 100644 index 0000000000..0860e10a7a --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getOperationResponseHeader.ts @@ -0,0 +1,13 @@ +import type { OperationResponse } from "../../../client/interfaces/OperationResponse" + +export const getOperationResponseHeader = ( + operationResponses: OperationResponse[] +): string | null => { + const header = operationResponses.find((operationResponses) => { + return operationResponses.in === "header" + }) + if (header) { + return header.name + } + return null +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getOperationResponses.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getOperationResponses.ts new file mode 100644 index 0000000000..8ef8ad0cfe --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getOperationResponses.ts @@ -0,0 +1,38 @@ +import type { OperationResponse } from "../../../client/interfaces/OperationResponse" +import type { OpenApi } from "../interfaces/OpenApi" +import type { OpenApiResponse } from "../interfaces/OpenApiResponse" +import type { OpenApiResponses } from "../interfaces/OpenApiResponses" +import { getOperationResponse } from "./getOperationResponse" +import { getOperationResponseCode } from "./getOperationResponseCode" +import { getRef } from "./getRef" + +export const getOperationResponses = ( + openApi: OpenApi, + responses: OpenApiResponses +): OperationResponse[] => { + const operationResponses: OperationResponse[] = [] + + // Iterate over each response code and get the + // status code and response message (if any). + for (const code in responses) { + if (responses.hasOwnProperty(code)) { + const responseOrReference = responses[code] + const response = getRef(openApi, responseOrReference) + const responseCode = getOperationResponseCode(code) + + if (responseCode) { + const operationResponse = getOperationResponse( + openApi, + response, + responseCode + ) + operationResponses.push(operationResponse) + } + } + } + + // Sort the responses to 2XX success codes come before 4XX and 5XX error codes. + return operationResponses.sort((a, b): number => { + return a.code < b.code ? -1 : a.code > b.code ? 1 : 0 + }) +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getOperationResults.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getOperationResults.ts new file mode 100644 index 0000000000..d93f501072 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getOperationResults.ts @@ -0,0 +1,56 @@ +import type { Model } from "../../../client/interfaces/Model" +import type { OperationResponse } from "../../../client/interfaces/OperationResponse" + +const areEqual = (a: Model, b: Model): boolean => { + const equal = + a.type === b.type && a.base === b.base && a.template === b.template + if (equal && a.link && b.link) { + return areEqual(a.link, b.link) + } + return equal +} + +export const getOperationResults = ( + operationResponses: OperationResponse[] +): OperationResponse[] => { + const operationResults: OperationResponse[] = [] + + // Filter out success response codes, but skip "204 No Content" + operationResponses.forEach((operationResponse) => { + const { code } = operationResponse + if (code && code !== 204 && code >= 200 && code < 300) { + operationResults.push(operationResponse) + } + }) + + if (!operationResults.length) { + operationResults.push({ + spec: { description: "" }, + in: "response", + name: "", + code: 200, + description: "", + export: "generic", + type: "void", + base: "void", + template: null, + link: null, + isDefinition: false, + isReadOnly: false, + isRequired: false, + isNullable: false, + imports: [], + enum: [], + enums: [], + properties: [], + }) + } + + return operationResults.filter((operationResult, index, arr) => { + return ( + arr.findIndex((item) => { + return areEqual(item, operationResult) + }) === index + ) + }) +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getRef.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getRef.ts new file mode 100644 index 0000000000..40657bf866 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getRef.ts @@ -0,0 +1,32 @@ +import type { OpenApi } from "../interfaces/OpenApi" +import type { OpenApiReference } from "../interfaces/OpenApiReference" + +const ESCAPED_REF_SLASH = /~1/g +const ESCAPED_REF_TILDE = /~0/g + +export const getRef = (openApi: OpenApi, item: T & OpenApiReference): T => { + if (item.$ref) { + // Fetch the paths to the definitions, this converts: + // "#/components/schemas/Form" to ["components", "schemas", "Form"] + const paths = item.$ref + .replace(/^#/g, "") + .split("/") + .filter((item) => item) + + // Try to find the reference by walking down the path, + // if we cannot find it, then we throw an error. + let result: any = openApi + paths.forEach((path) => { + const decodedPath = decodeURIComponent( + path.replace(ESCAPED_REF_SLASH, "/").replace(ESCAPED_REF_TILDE, "~") + ) + if (result.hasOwnProperty(decodedPath)) { + result = result[decodedPath] + } else { + throw new Error(`Could not find reference: "${item.$ref}"`) + } + }) + return result as T + } + return item as T +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getRequiredPropertiesFromComposition.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getRequiredPropertiesFromComposition.ts new file mode 100644 index 0000000000..d9912d8da2 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getRequiredPropertiesFromComposition.ts @@ -0,0 +1,33 @@ +import type { Model } from "../../../client/interfaces/Model" +import type { OpenApi } from "../interfaces/OpenApi" +import type { OpenApiSchema } from "../interfaces/OpenApiSchema" +import type { getModel } from "./getModel" +import { getRef } from "./getRef" + +// Fix for circular dependency +export type GetModelFn = typeof getModel + +export const getRequiredPropertiesFromComposition = ( + openApi: OpenApi, + required: string[], + definitions: OpenApiSchema[], + getModel: GetModelFn +): Model[] => { + return definitions + .reduce((properties, definition) => { + if (definition.$ref) { + const schema = getRef(openApi, definition) + return [...properties, ...getModel(openApi, schema).properties] + } + return [...properties, ...getModel(openApi, definition).properties] + }, [] as Model[]) + .filter((property) => { + return !property.isRequired && required.includes(property.name) + }) + .map((property) => { + return { + ...property, + isRequired: true, + } + }) +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getServer.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getServer.ts new file mode 100644 index 0000000000..45cba010d6 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getServer.ts @@ -0,0 +1,13 @@ +import type { OpenApi } from "../interfaces/OpenApi" + +export const getServer = (openApi: OpenApi): string => { + const server = openApi.servers?.[0] + const variables = server?.variables || {} + let url = server?.url || "" + for (const variable in variables) { + if (variables.hasOwnProperty(variable)) { + url = url.replace(`{${variable}}`, variables[variable].default) + } + } + return url.replace(/\/$/g, "") +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getServiceName.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getServiceName.ts new file mode 100644 index 0000000000..bc25d3aa73 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getServiceName.ts @@ -0,0 +1,13 @@ +import camelCase from "camelcase" + +/** + * Convert the input value to a correct service name. This converts + * the input string to PascalCase. + */ +export const getServiceName = (value: string): string => { + const clean = value + .replace(/^[^a-zA-Z]+/g, "") + .replace(/[^\w\-]+/g, "-") + .trim() + return camelCase(clean, { pascalCase: true }) +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getServiceVersion.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getServiceVersion.ts new file mode 100644 index 0000000000..d4ba5fefd1 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getServiceVersion.ts @@ -0,0 +1,8 @@ +/** + * Convert the service version to 'normal' version. + * This basically removes any "v" prefix from the version string. + * @param version + */ +export const getServiceVersion = (version = "1.0"): string => { + return String(version).replace(/^v/gi, "") +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getServices.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getServices.ts new file mode 100644 index 0000000000..b255107584 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getServices.ts @@ -0,0 +1,26 @@ +import type { Service } from "../../../client/interfaces/Service" +import type { OpenApi } from "../interfaces/OpenApi" +import { listOperations } from "./listOperations" + +/** + * Get the OpenAPI services + */ +export const getServices = (openApi: OpenApi): Service[] => { + const services = new Map() + const operations = listOperations(openApi) + for (const operation of operations) { + // If we have already declared a service, then we should fetch that and + // append the new method to it. Otherwise we should create a new service object. + const service: Service = services.get(operation.service) || { + name: operation.service, + operations: [], + imports: [], + } + + // Push the operation in the service + service.operations.push(operation) + service.imports.push(...operation.imports) + services.set(operation.service, service) + } + return Array.from(services.values()) +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getType.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getType.ts new file mode 100644 index 0000000000..597d8bbf89 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/getType.ts @@ -0,0 +1,85 @@ +import type { Type } from "../../../client/interfaces/Type" +import { isDefined } from "../../../utils/isDefined" +import { getMappedType } from "./getMappedType" +import { stripNamespace } from "./stripNamespace" + +const encode = (value: string): string => { + return value.replace(/^[^a-zA-Z_$]+/g, "").replace(/[^\w$]+/g, "_") +} + +/** + * Parse any string value into a type object. + * @param type String or String[] value like "integer", "Link[Model]" or ["string", "null"]. + * @param format String value like "binary" or "date". + */ +export const getType = ( + type: string | string[] = "any", + format?: string +): Type => { + const result: Type = { + type: "any", + base: "any", + template: null, + imports: [], + isNullable: false, + } + + // Special case for JSON Schema spec (december 2020, page 17), + // that allows type to be an array of primitive types... + if (Array.isArray(type)) { + const joinedType = type + .filter((value) => value !== "null") + .map((value) => getMappedType(value, format)) + .filter(isDefined) + .join(" | ") + result.type = joinedType + result.base = joinedType + result.isNullable = type.includes("null") + return result + } + + const mapped = getMappedType(type, format) + if (mapped) { + result.type = mapped + result.base = mapped + return result + } + + const typeWithoutNamespace = decodeURIComponent(stripNamespace(type)) + + if (/\[.*\]$/g.test(typeWithoutNamespace)) { + const matches = typeWithoutNamespace.match(/(.*?)\[(.*)\]$/) + if (matches?.length) { + const match1 = getType(encode(matches[1])) + const match2 = getType(encode(matches[2])) + + if (match1.type === "any[]") { + result.type = `${match2.type}[]` + result.base = `${match2.type}` + match1.imports = [] + } else if (match2.type) { + result.type = `${match1.type}<${match2.type}>` + result.base = match1.type + result.template = match2.type + } else { + result.type = match1.type + result.base = match1.type + result.template = match1.type + } + + result.imports.push(...match1.imports) + result.imports.push(...match2.imports) + return result + } + } + + if (typeWithoutNamespace) { + const type = encode(typeWithoutNamespace) + result.type = type + result.base = type + result.imports.push(type) + return result + } + + return result +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/listOperations.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/listOperations.ts new file mode 100644 index 0000000000..83d410a38f --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/listOperations.ts @@ -0,0 +1,49 @@ +import { OpenApi } from "../interfaces/OpenApi" +import { Operation } from "../../../client/interfaces/Operation" +import { getOperationParameters } from "./getOperationParameters" +import { unique } from "../../../utils/unique" +import { getOperation } from "./getOperation" + +export const listOperations = (openApi: OpenApi): Operation[] => { + const operations: Operation[] = [] + for (const url in openApi.paths) { + if (openApi.paths.hasOwnProperty(url)) { + // Grab path and parse any global path parameters + const path = openApi.paths[url] + const pathParams = getOperationParameters(openApi, path.parameters || []) + + // Parse all the methods for this path + for (const method in path) { + if (path.hasOwnProperty(method)) { + switch (method) { + case "get": + case "put": + case "post": + case "delete": + case "options": + case "head": + case "patch": + // Each method contains an OpenAPI operation, we parse the operation + const op = path[method]! + const tags = op.tags?.length + ? op.tags.filter(unique) + : ["Default"] + tags.forEach((tag) => { + const operation = getOperation( + openApi, + url, + method, + tag, + op, + pathParams + ) + operations.push(operation) + }) + break + } + } + } + } + } + return operations +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/sortByRequired.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/sortByRequired.ts new file mode 100644 index 0000000000..a79b90c46d --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/sortByRequired.ts @@ -0,0 +1,12 @@ +import type { OperationParameter } from "../../../client/interfaces/OperationParameter" + +export const sortByRequired = ( + a: OperationParameter, + b: OperationParameter +): number => { + const aNeedsValue = a.isRequired && a.default === undefined + const bNeedsValue = b.isRequired && b.default === undefined + if (aNeedsValue && !bNeedsValue) return -1 + if (bNeedsValue && !aNeedsValue) return 1 + return 0 +} diff --git a/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/stripNamespace.ts b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/stripNamespace.ts new file mode 100644 index 0000000000..f59aa5d47e --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/openApi/v3/parser/stripNamespace.ts @@ -0,0 +1,17 @@ +/** + * Strip (OpenAPI) namespaces fom values. + * @param value + */ +export const stripNamespace = (value: string): string => { + return value + .trim() + .replace(/^#\/components\/schemas\//, "") + .replace(/^#\/components\/responses\//, "") + .replace(/^#\/components\/parameters\//, "") + .replace(/^#\/components\/examples\//, "") + .replace(/^#\/components\/requestBodies\//, "") + .replace(/^#\/components\/headers\//, "") + .replace(/^#\/components\/securitySchemes\//, "") + .replace(/^#\/components\/links\//, "") + .replace(/^#\/components\/callbacks\//, "") +} diff --git a/packages/oas/openapi-typescript-codegen/src/templates/client.hbs b/packages/oas/openapi-typescript-codegen/src/templates/client.hbs new file mode 100644 index 0000000000..0408c59307 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/client.hbs @@ -0,0 +1,40 @@ +{{>header}} + +import type { BaseHttpRequest } from './core/BaseHttpRequest'; +import type { OpenAPIConfig } from './core/OpenAPI'; +import { {{{httpRequest}}} } from './core/{{{httpRequest}}}'; + +{{#if services}} +{{#each services}} +import { {{{name}}}{{{@root.postfix}}} } from './services/{{{name}}}{{{@root.postfix}}}'; +{{/each}} +{{/if}} + +type HttpRequestConstructor = new (config: OpenAPIConfig) => BaseHttpRequest; + +export class {{{clientName}}} { + + {{#each services}} + public readonly {{{camelCase name}}}: {{{name}}}{{{@root.postfix}}}; + {{/each}} + + public readonly request: BaseHttpRequest; + + constructor(config?: Partial, HttpRequest: HttpRequestConstructor = {{{httpRequest}}}) { + this.request = new HttpRequest({ + BASE: config?.BASE ?? '{{{server}}}', + VERSION: config?.VERSION ?? '{{{version}}}', + WITH_CREDENTIALS: config?.WITH_CREDENTIALS ?? false, + CREDENTIALS: config?.CREDENTIALS ?? 'include', + TOKEN: config?.TOKEN, + USERNAME: config?.USERNAME, + PASSWORD: config?.PASSWORD, + HEADERS: config?.HEADERS, + ENCODE_PATH: config?.ENCODE_PATH, + }); + + {{#each services}} + this.{{{camelCase name}}} = new {{{name}}}{{{@root.postfix}}}(this.request); + {{/each}} + } +} diff --git a/packages/oas/openapi-typescript-codegen/src/templates/core/ApiError.hbs b/packages/oas/openapi-typescript-codegen/src/templates/core/ApiError.hbs new file mode 100644 index 0000000000..ab6849d189 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/core/ApiError.hbs @@ -0,0 +1,23 @@ +{{>header}} + +import type { ApiRequestOptions } from './ApiRequestOptions'; +import type { ApiResult } from './ApiResult'; + +export class ApiError extends Error { + public readonly url: string; + public readonly status: number; + public readonly statusText: string; + public readonly body: any; + public readonly request: ApiRequestOptions; + + constructor(request: ApiRequestOptions, response: ApiResult, message: string) { + super(message); + + this.name = 'ApiError'; + this.url = response.url; + this.status = response.status; + this.statusText = response.statusText; + this.body = response.body; + this.request = request; + } +} diff --git a/packages/oas/openapi-typescript-codegen/src/templates/core/ApiRequestOptions.hbs b/packages/oas/openapi-typescript-codegen/src/templates/core/ApiRequestOptions.hbs new file mode 100644 index 0000000000..355929a712 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/core/ApiRequestOptions.hbs @@ -0,0 +1,15 @@ +{{>header}} + +export type ApiRequestOptions = { + readonly method: 'GET' | 'PUT' | 'POST' | 'DELETE' | 'OPTIONS' | 'HEAD' | 'PATCH'; + readonly url: string; + readonly path?: Record; + readonly cookies?: Record; + readonly headers?: Record; + readonly query?: Record; + readonly formData?: Record; + readonly body?: any; + readonly mediaType?: string; + readonly responseHeader?: string; + readonly errors?: Record; +}; diff --git a/packages/oas/openapi-typescript-codegen/src/templates/core/ApiResult.hbs b/packages/oas/openapi-typescript-codegen/src/templates/core/ApiResult.hbs new file mode 100644 index 0000000000..a768b8c5ae --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/core/ApiResult.hbs @@ -0,0 +1,9 @@ +{{>header}} + +export type ApiResult = { + readonly url: string; + readonly ok: boolean; + readonly status: number; + readonly statusText: string; + readonly body: any; +}; diff --git a/packages/oas/openapi-typescript-codegen/src/templates/core/BaseHttpRequest.hbs b/packages/oas/openapi-typescript-codegen/src/templates/core/BaseHttpRequest.hbs new file mode 100644 index 0000000000..c4c992a825 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/core/BaseHttpRequest.hbs @@ -0,0 +1,12 @@ +{{>header}} + +import type { ApiRequestOptions } from './ApiRequestOptions'; +import type { CancelablePromise } from './CancelablePromise'; +import type { OpenAPIConfig } from './OpenAPI'; + +export abstract class BaseHttpRequest { + + constructor(public readonly config: OpenAPIConfig) {} + + public abstract request(options: ApiRequestOptions): CancelablePromise; +} diff --git a/packages/oas/openapi-typescript-codegen/src/templates/core/CancelablePromise.hbs b/packages/oas/openapi-typescript-codegen/src/templates/core/CancelablePromise.hbs new file mode 100644 index 0000000000..b708b4edc4 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/core/CancelablePromise.hbs @@ -0,0 +1,127 @@ +{{>header}} + +export class CancelError extends Error { + + constructor(message: string) { + super(message); + this.name = 'CancelError'; + } + + public get isCancelled(): boolean { + return true; + } +} + +export interface OnCancel { + readonly isResolved: boolean; + readonly isRejected: boolean; + readonly isCancelled: boolean; + + (cancelHandler: () => void): void; +} + +export class CancelablePromise implements Promise { + readonly [Symbol.toStringTag]!: string; + + private _isResolved: boolean; + private _isRejected: boolean; + private _isCancelled: boolean; + private readonly _cancelHandlers: (() => void)[]; + private readonly _promise: Promise; + private _resolve?: (value: T | PromiseLike) => void; + private _reject?: (reason?: any) => void; + + constructor( + executor: ( + resolve: (value: T | PromiseLike) => void, + reject: (reason?: any) => void, + onCancel: OnCancel + ) => void + ) { + this._isResolved = false; + this._isRejected = false; + this._isCancelled = false; + this._cancelHandlers = []; + this._promise = new Promise((resolve, reject) => { + this._resolve = resolve; + this._reject = reject; + + const onResolve = (value: T | PromiseLike): void => { + if (this._isResolved || this._isRejected || this._isCancelled) { + return; + } + this._isResolved = true; + this._resolve?.(value); + }; + + const onReject = (reason?: any): void => { + if (this._isResolved || this._isRejected || this._isCancelled) { + return; + } + this._isRejected = true; + this._reject?.(reason); + }; + + const onCancel = (cancelHandler: () => void): void => { + if (this._isResolved || this._isRejected || this._isCancelled) { + return; + } + this._cancelHandlers.push(cancelHandler); + }; + + Object.defineProperty(onCancel, 'isResolved', { + get: (): boolean => this._isResolved, + }); + + Object.defineProperty(onCancel, 'isRejected', { + get: (): boolean => this._isRejected, + }); + + Object.defineProperty(onCancel, 'isCancelled', { + get: (): boolean => this._isCancelled, + }); + + return executor(onResolve, onReject, onCancel as OnCancel); + }); + } + + public then( + onFulfilled?: ((value: T) => TResult1 | PromiseLike) | null, + onRejected?: ((reason: any) => TResult2 | PromiseLike) | null + ): Promise { + return this._promise.then(onFulfilled, onRejected); + } + + public catch( + onRejected?: ((reason: any) => TResult | PromiseLike) | null + ): Promise { + return this._promise.catch(onRejected); + } + + public finally(onFinally?: (() => void) | null): Promise { + return this._promise.finally(onFinally); + } + + public cancel(): void { + if (this._isResolved || this._isRejected || this._isCancelled) { + return; + } + this._isCancelled = true; + if (this._cancelHandlers.length) { + try { + for (const cancelHandler of this._cancelHandlers) { + cancelHandler(); + } + } catch (error) { + console.warn('Cancellation threw an error', error); + return; + } + } + this._cancelHandlers.length = 0; + this._reject?.(new CancelError('Request aborted')); + } + + public get isCancelled(): boolean { + return this._isCancelled; + } +} diff --git a/packages/oas/openapi-typescript-codegen/src/templates/core/HookUtils.hbs b/packages/oas/openapi-typescript-codegen/src/templates/core/HookUtils.hbs new file mode 100644 index 0000000000..266a94ef6d --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/core/HookUtils.hbs @@ -0,0 +1,23 @@ +{{>header}} + +import { UseQueryOptions, UseMutationOptions, QueryKey } from "react-query" + +export type UseQueryOptionsWrapper< + TQueryFnData = unknown, + TError = unknown, + TData = TQueryFnData, + TQueryKey extends QueryKey = QueryKey +> = Omit< + UseQueryOptions, + "queryKey" | "queryFn" +> + +export type UseMutationOptionsWrapper< + TData = unknown, + TError = unknown, + TVariables = void, + TContext = unknown +> = Omit< + UseMutationOptions, + 'mutationKey' | 'mutationFn' +> diff --git a/packages/oas/openapi-typescript-codegen/src/templates/core/HttpRequest.hbs b/packages/oas/openapi-typescript-codegen/src/templates/core/HttpRequest.hbs new file mode 100644 index 0000000000..1e19036ea2 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/core/HttpRequest.hbs @@ -0,0 +1,24 @@ +{{>header}} + +import type { ApiRequestOptions } from './ApiRequestOptions'; +import { BaseHttpRequest } from './BaseHttpRequest'; +import type { CancelablePromise } from './CancelablePromise'; +import type { OpenAPIConfig } from './OpenAPI'; +import { request as __request } from './request'; + +export class {{httpRequest}} extends BaseHttpRequest { + + constructor(config: OpenAPIConfig) { + super(config); + } + + /** + * Request method + * @param options The request options from the service + * @returns CancelablePromise + * @throws ApiError + */ + public override request(options: ApiRequestOptions): CancelablePromise { + return __request(this.config, options); + } +} diff --git a/packages/oas/openapi-typescript-codegen/src/templates/core/OpenAPI.hbs b/packages/oas/openapi-typescript-codegen/src/templates/core/OpenAPI.hbs new file mode 100644 index 0000000000..19e66fbf18 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/core/OpenAPI.hbs @@ -0,0 +1,30 @@ +{{>header}} + +import type { ApiRequestOptions } from './ApiRequestOptions'; + +type Resolver = (options: ApiRequestOptions) => Promise; +type Headers = Record; + +export type OpenAPIConfig = { + BASE: string; + VERSION: string; + WITH_CREDENTIALS: boolean; + CREDENTIALS: 'include' | 'omit' | 'same-origin'; + TOKEN?: string | Resolver; + USERNAME?: string | Resolver; + PASSWORD?: string | Resolver; + HEADERS?: Headers | Resolver; + ENCODE_PATH?: (path: string) => string; +}; + +export const OpenAPI: OpenAPIConfig = { + BASE: '{{{server}}}', + VERSION: '{{{version}}}', + WITH_CREDENTIALS: false, + CREDENTIALS: 'include', + TOKEN: undefined, + USERNAME: undefined, + PASSWORD: undefined, + HEADERS: undefined, + ENCODE_PATH: undefined, +}; diff --git a/packages/oas/openapi-typescript-codegen/src/templates/core/axios/getHeaders.hbs b/packages/oas/openapi-typescript-codegen/src/templates/core/axios/getHeaders.hbs new file mode 100644 index 0000000000..e8a4afa382 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/core/axios/getHeaders.hbs @@ -0,0 +1,42 @@ +const getHeaders = async (config: OpenAPIConfig, options: ApiRequestOptions, formData?: FormData): Promise> => { + const token = await resolve(options, config.TOKEN); + const username = await resolve(options, config.USERNAME); + const password = await resolve(options, config.PASSWORD); + const additionalHeaders = await resolve(options, config.HEADERS); + const formHeaders = typeof formData?.getHeaders === 'function' && formData?.getHeaders() || {} + + const headers = Object.entries({ + Accept: 'application/json', + ...additionalHeaders, + ...options.headers, + ...formHeaders, + }) + .filter(([_, value]) => isDefined(value)) + .reduce((headers, [key, value]) => ({ + ...headers, + [key]: String(value), + }), {} as Record); + + if (isStringWithValue(token)) { + headers['Authorization'] = `Bearer ${token}`; + } + + if (isStringWithValue(username) && isStringWithValue(password)) { + const credentials = base64(`${username}:${password}`); + headers['Authorization'] = `Basic ${credentials}`; + } + + if (options.body) { + if (options.mediaType) { + headers['Content-Type'] = options.mediaType; + } else if (isBlob(options.body)) { + headers['Content-Type'] = options.body.type || 'application/octet-stream'; + } else if (isString(options.body)) { + headers['Content-Type'] = 'text/plain'; + } else if (!isFormData(options.body)) { + headers['Content-Type'] = 'application/json'; + } + } + + return headers; +}; diff --git a/packages/oas/openapi-typescript-codegen/src/templates/core/axios/getRequestBody.hbs b/packages/oas/openapi-typescript-codegen/src/templates/core/axios/getRequestBody.hbs new file mode 100644 index 0000000000..78abe5dfae --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/core/axios/getRequestBody.hbs @@ -0,0 +1,6 @@ +const getRequestBody = (options: ApiRequestOptions): any => { + if (options.body) { + return options.body; + } + return undefined; +}; diff --git a/packages/oas/openapi-typescript-codegen/src/templates/core/axios/getResponseBody.hbs b/packages/oas/openapi-typescript-codegen/src/templates/core/axios/getResponseBody.hbs new file mode 100644 index 0000000000..0d0287882c --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/core/axios/getResponseBody.hbs @@ -0,0 +1,6 @@ +const getResponseBody = (response: AxiosResponse): any => { + if (response.status !== 204) { + return response.data; + } + return undefined; +}; diff --git a/packages/oas/openapi-typescript-codegen/src/templates/core/axios/getResponseHeader.hbs b/packages/oas/openapi-typescript-codegen/src/templates/core/axios/getResponseHeader.hbs new file mode 100644 index 0000000000..ba83fd2e0b --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/core/axios/getResponseHeader.hbs @@ -0,0 +1,9 @@ +const getResponseHeader = (response: AxiosResponse, responseHeader?: string): string | undefined => { + if (responseHeader) { + const content = response.headers[responseHeader]; + if (isString(content)) { + return content; + } + } + return undefined; +}; diff --git a/packages/oas/openapi-typescript-codegen/src/templates/core/axios/request.hbs b/packages/oas/openapi-typescript-codegen/src/templates/core/axios/request.hbs new file mode 100644 index 0000000000..19ebd5fb96 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/core/axios/request.hbs @@ -0,0 +1,101 @@ +{{>header}} + +import axios from 'axios'; +import type { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios'; +import FormData from 'form-data'; + +import { ApiError } from './ApiError'; +import type { ApiRequestOptions } from './ApiRequestOptions'; +import type { ApiResult } from './ApiResult'; +import { CancelablePromise } from './CancelablePromise'; +import type { OnCancel } from './CancelablePromise'; +import type { OpenAPIConfig } from './OpenAPI'; + +{{>functions/isDefined}} + + +{{>functions/isString}} + + +{{>functions/isStringWithValue}} + + +{{>functions/isBlob}} + + +{{>functions/isFormData}} + + +{{>functions/isSuccess}} + + +{{>functions/base64}} + + +{{>functions/getQueryString}} + + +{{>functions/getUrl}} + + +{{>functions/getFormData}} + + +{{>functions/resolve}} + + +{{>axios/getHeaders}} + + +{{>axios/getRequestBody}} + + +{{>axios/sendRequest}} + + +{{>axios/getResponseHeader}} + + +{{>axios/getResponseBody}} + + +{{>functions/catchErrorCodes}} + + +/** + * Request method + * @param config The OpenAPI configuration object + * @param options The request options from the service + * @returns CancelablePromise + * @throws ApiError + */ +export const request = (config: OpenAPIConfig, options: ApiRequestOptions): CancelablePromise => { + return new CancelablePromise(async (resolve, reject, onCancel) => { + try { + const url = getUrl(config, options); + const formData = getFormData(options); + const body = getRequestBody(options); + const headers = await getHeaders(config, options, formData); + + if (!onCancel.isCancelled) { + const response = await sendRequest(config, options, url, body, formData, headers, onCancel); + const responseBody = getResponseBody(response); + const responseHeader = getResponseHeader(response, options.responseHeader); + + const result: ApiResult = { + url, + ok: isSuccess(response.status), + status: response.status, + statusText: response.statusText, + body: responseHeader ?? responseBody, + }; + + catchErrorCodes(options, result); + + resolve(result.body); + } + } catch (error) { + reject(error); + } + }); +}; diff --git a/packages/oas/openapi-typescript-codegen/src/templates/core/axios/sendRequest.hbs b/packages/oas/openapi-typescript-codegen/src/templates/core/axios/sendRequest.hbs new file mode 100644 index 0000000000..0b871ea366 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/core/axios/sendRequest.hbs @@ -0,0 +1,32 @@ +const sendRequest = async ( + config: OpenAPIConfig, + options: ApiRequestOptions, + url: string, + body: any, + formData: FormData | undefined, + headers: Record, + onCancel: OnCancel +): Promise> => { + const source = axios.CancelToken.source(); + + const requestConfig: AxiosRequestConfig = { + url, + headers, + data: body ?? formData, + method: options.method, + withCredentials: config.WITH_CREDENTIALS, + cancelToken: source.token, + }; + + onCancel(() => source.cancel('The user aborted a request.')); + + try { + return await axios.request(requestConfig); + } catch (error) { + const axiosError = error as AxiosError; + if (axiosError.response) { + return axiosError.response; + } + throw error; + } +}; diff --git a/packages/oas/openapi-typescript-codegen/src/templates/core/fetch/getHeaders.hbs b/packages/oas/openapi-typescript-codegen/src/templates/core/fetch/getHeaders.hbs new file mode 100644 index 0000000000..41a65005c0 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/core/fetch/getHeaders.hbs @@ -0,0 +1,40 @@ +const getHeaders = async (config: OpenAPIConfig, options: ApiRequestOptions): Promise => { + const token = await resolve(options, config.TOKEN); + const username = await resolve(options, config.USERNAME); + const password = await resolve(options, config.PASSWORD); + const additionalHeaders = await resolve(options, config.HEADERS); + + const headers = Object.entries({ + Accept: 'application/json', + ...additionalHeaders, + ...options.headers, + }) + .filter(([_, value]) => isDefined(value)) + .reduce((headers, [key, value]) => ({ + ...headers, + [key]: String(value), + }), {} as Record); + + if (isStringWithValue(token)) { + headers['Authorization'] = `Bearer ${token}`; + } + + if (isStringWithValue(username) && isStringWithValue(password)) { + const credentials = base64(`${username}:${password}`); + headers['Authorization'] = `Basic ${credentials}`; + } + + if (options.body) { + if (options.mediaType) { + headers['Content-Type'] = options.mediaType; + } else if (isBlob(options.body)) { + headers['Content-Type'] = options.body.type || 'application/octet-stream'; + } else if (isString(options.body)) { + headers['Content-Type'] = 'text/plain'; + } else if (!isFormData(options.body)) { + headers['Content-Type'] = 'application/json'; + } + } + + return new Headers(headers); +}; diff --git a/packages/oas/openapi-typescript-codegen/src/templates/core/fetch/getRequestBody.hbs b/packages/oas/openapi-typescript-codegen/src/templates/core/fetch/getRequestBody.hbs new file mode 100644 index 0000000000..b2fc7e21cd --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/core/fetch/getRequestBody.hbs @@ -0,0 +1,12 @@ +const getRequestBody = (options: ApiRequestOptions): any => { + if (options.body !== undefined) { + if (options.mediaType?.includes('/json')) { + return JSON.stringify(options.body) + } else if (isString(options.body) || isBlob(options.body) || isFormData(options.body)) { + return options.body; + } else { + return JSON.stringify(options.body); + } + } + return undefined; +}; diff --git a/packages/oas/openapi-typescript-codegen/src/templates/core/fetch/getResponseBody.hbs b/packages/oas/openapi-typescript-codegen/src/templates/core/fetch/getResponseBody.hbs new file mode 100644 index 0000000000..556c1f03f8 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/core/fetch/getResponseBody.hbs @@ -0,0 +1,19 @@ +const getResponseBody = async (response: Response): Promise => { + if (response.status !== 204) { + try { + const contentType = response.headers.get('Content-Type'); + if (contentType) { + const jsonTypes = ['application/json', 'application/problem+json'] + const isJSON = jsonTypes.some(type => contentType.toLowerCase().startsWith(type)); + if (isJSON) { + return await response.json(); + } else { + return await response.text(); + } + } + } catch (error) { + console.error(error); + } + } + return undefined; +}; diff --git a/packages/oas/openapi-typescript-codegen/src/templates/core/fetch/getResponseHeader.hbs b/packages/oas/openapi-typescript-codegen/src/templates/core/fetch/getResponseHeader.hbs new file mode 100644 index 0000000000..531fb84f1f --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/core/fetch/getResponseHeader.hbs @@ -0,0 +1,9 @@ +const getResponseHeader = (response: Response, responseHeader?: string): string | undefined => { + if (responseHeader) { + const content = response.headers.get(responseHeader); + if (isString(content)) { + return content; + } + } + return undefined; +}; diff --git a/packages/oas/openapi-typescript-codegen/src/templates/core/fetch/request.hbs b/packages/oas/openapi-typescript-codegen/src/templates/core/fetch/request.hbs new file mode 100644 index 0000000000..4af6f9440a --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/core/fetch/request.hbs @@ -0,0 +1,94 @@ +{{>header}} + +import { ApiError } from './ApiError'; +import type { ApiRequestOptions } from './ApiRequestOptions'; +import type { ApiResult } from './ApiResult'; +import { CancelablePromise } from './CancelablePromise'; +import type { OnCancel } from './CancelablePromise'; +import type { OpenAPIConfig } from './OpenAPI'; + +{{>functions/isDefined}} + + +{{>functions/isString}} + + +{{>functions/isStringWithValue}} + + +{{>functions/isBlob}} + + +{{>functions/isFormData}} + + +{{>functions/base64}} + + +{{>functions/getQueryString}} + + +{{>functions/getUrl}} + + +{{>functions/getFormData}} + + +{{>functions/resolve}} + + +{{>fetch/getHeaders}} + + +{{>fetch/getRequestBody}} + + +{{>fetch/sendRequest}} + + +{{>fetch/getResponseHeader}} + + +{{>fetch/getResponseBody}} + + +{{>functions/catchErrorCodes}} + + +/** + * Request method + * @param config The OpenAPI configuration object + * @param options The request options from the service + * @returns CancelablePromise + * @throws ApiError + */ +export const request = (config: OpenAPIConfig, options: ApiRequestOptions): CancelablePromise => { + return new CancelablePromise(async (resolve, reject, onCancel) => { + try { + const url = getUrl(config, options); + const formData = getFormData(options); + const body = getRequestBody(options); + const headers = await getHeaders(config, options); + + if (!onCancel.isCancelled) { + const response = await sendRequest(config, options, url, body, formData, headers, onCancel); + const responseBody = await getResponseBody(response); + const responseHeader = getResponseHeader(response, options.responseHeader); + + const result: ApiResult = { + url, + ok: response.ok, + status: response.status, + statusText: response.statusText, + body: responseHeader ?? responseBody, + }; + + catchErrorCodes(options, result); + + resolve(result.body); + } + } catch (error) { + reject(error); + } + }); +}; diff --git a/packages/oas/openapi-typescript-codegen/src/templates/core/fetch/sendRequest.hbs b/packages/oas/openapi-typescript-codegen/src/templates/core/fetch/sendRequest.hbs new file mode 100644 index 0000000000..73f71f428d --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/core/fetch/sendRequest.hbs @@ -0,0 +1,26 @@ +export const sendRequest = async ( + config: OpenAPIConfig, + options: ApiRequestOptions, + url: string, + body: any, + formData: FormData | undefined, + headers: Headers, + onCancel: OnCancel +): Promise => { + const controller = new AbortController(); + + const request: RequestInit = { + headers, + body: body ?? formData, + method: options.method, + signal: controller.signal, + }; + + if (config.WITH_CREDENTIALS) { + request.credentials = config.CREDENTIALS; + } + + onCancel(() => controller.abort()); + + return await fetch(url, request); +}; diff --git a/packages/oas/openapi-typescript-codegen/src/templates/core/functions/base64.hbs b/packages/oas/openapi-typescript-codegen/src/templates/core/functions/base64.hbs new file mode 100644 index 0000000000..be275b8510 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/core/functions/base64.hbs @@ -0,0 +1,8 @@ +const base64 = (str: string): string => { + try { + return btoa(str); + } catch (err) { + // @ts-ignore + return Buffer.from(str).toString('base64'); + } +}; diff --git a/packages/oas/openapi-typescript-codegen/src/templates/core/functions/catchErrorCodes.hbs b/packages/oas/openapi-typescript-codegen/src/templates/core/functions/catchErrorCodes.hbs new file mode 100644 index 0000000000..8e87583a54 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/core/functions/catchErrorCodes.hbs @@ -0,0 +1,21 @@ +const catchErrorCodes = (options: ApiRequestOptions, result: ApiResult): void => { + const errors: Record = { + 400: 'Bad Request', + 401: 'Unauthorized', + 403: 'Forbidden', + 404: 'Not Found', + 500: 'Internal Server Error', + 502: 'Bad Gateway', + 503: 'Service Unavailable', + ...options.errors, + } + + const error = errors[result.status]; + if (error) { + throw new ApiError(options, result, error); + } + + if (!result.ok) { + throw new ApiError(options, result, 'Generic Error'); + } +}; diff --git a/packages/oas/openapi-typescript-codegen/src/templates/core/functions/getFormData.hbs b/packages/oas/openapi-typescript-codegen/src/templates/core/functions/getFormData.hbs new file mode 100644 index 0000000000..3a74b8f3af --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/core/functions/getFormData.hbs @@ -0,0 +1,26 @@ +const getFormData = (options: ApiRequestOptions): FormData | undefined => { + if (options.formData) { + const formData = new FormData(); + + const process = (key: string, value: any) => { + if (isString(value) || isBlob(value)) { + formData.append(key, value); + } else { + formData.append(key, JSON.stringify(value)); + } + }; + + Object.entries(options.formData) + .filter(([_, value]) => isDefined(value)) + .forEach(([key, value]) => { + if (Array.isArray(value)) { + value.forEach(v => process(key, v)); + } else { + process(key, value); + } + }); + + return formData; + } + return undefined; +}; diff --git a/packages/oas/openapi-typescript-codegen/src/templates/core/functions/getQueryString.hbs b/packages/oas/openapi-typescript-codegen/src/templates/core/functions/getQueryString.hbs new file mode 100644 index 0000000000..eac37f5e47 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/core/functions/getQueryString.hbs @@ -0,0 +1,33 @@ +const getQueryString = (params: Record): string => { + const qs: string[] = []; + + const append = (key: string, value: any) => { + qs.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`); + }; + + const process = (key: string, value: any) => { + if (isDefined(value)) { + if (Array.isArray(value)) { + value.forEach(v => { + process(key, v); + }); + } else if (typeof value === 'object') { + Object.entries(value).forEach(([k, v]) => { + process(`${key}[${k}]`, v); + }); + } else { + append(key, value); + } + } + }; + + Object.entries(params).forEach(([key, value]) => { + process(key, value); + }); + + if (qs.length > 0) { + return `?${qs.join('&')}`; + } + + return ''; +}; diff --git a/packages/oas/openapi-typescript-codegen/src/templates/core/functions/getUrl.hbs b/packages/oas/openapi-typescript-codegen/src/templates/core/functions/getUrl.hbs new file mode 100644 index 0000000000..fe181ab2e7 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/core/functions/getUrl.hbs @@ -0,0 +1,18 @@ +const getUrl = (config: OpenAPIConfig, options: ApiRequestOptions): string => { + const encoder = config.ENCODE_PATH || encodeURI; + + const path = options.url + .replace('{api-version}', config.VERSION) + .replace(/{(.*?)}/g, (substring: string, group: string) => { + if (options.path?.hasOwnProperty(group)) { + return encoder(String(options.path[group])); + } + return substring; + }); + + const url = `${config.BASE}${path}`; + if (options.query) { + return `${url}${getQueryString(options.query)}`; + } + return url; +}; diff --git a/packages/oas/openapi-typescript-codegen/src/templates/core/functions/isBlob.hbs b/packages/oas/openapi-typescript-codegen/src/templates/core/functions/isBlob.hbs new file mode 100644 index 0000000000..caf44dddaf --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/core/functions/isBlob.hbs @@ -0,0 +1,12 @@ +const isBlob = (value: any): value is Blob => { + return ( + typeof value === 'object' && + typeof value.type === 'string' && + typeof value.stream === 'function' && + typeof value.arrayBuffer === 'function' && + typeof value.constructor === 'function' && + typeof value.constructor.name === 'string' && + /^(Blob|File)$/.test(value.constructor.name) && + /^(Blob|File)$/.test(value[Symbol.toStringTag]) + ); +}; diff --git a/packages/oas/openapi-typescript-codegen/src/templates/core/functions/isDefined.hbs b/packages/oas/openapi-typescript-codegen/src/templates/core/functions/isDefined.hbs new file mode 100644 index 0000000000..ef6d0fc79b --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/core/functions/isDefined.hbs @@ -0,0 +1,3 @@ +const isDefined = (value: T | null | undefined): value is Exclude => { + return value !== undefined && value !== null; +}; diff --git a/packages/oas/openapi-typescript-codegen/src/templates/core/functions/isFormData.hbs b/packages/oas/openapi-typescript-codegen/src/templates/core/functions/isFormData.hbs new file mode 100644 index 0000000000..8bcbed6b48 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/core/functions/isFormData.hbs @@ -0,0 +1,3 @@ +const isFormData = (value: any): value is FormData => { + return value instanceof FormData; +}; diff --git a/packages/oas/openapi-typescript-codegen/src/templates/core/functions/isString.hbs b/packages/oas/openapi-typescript-codegen/src/templates/core/functions/isString.hbs new file mode 100644 index 0000000000..b9888d56b8 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/core/functions/isString.hbs @@ -0,0 +1,3 @@ +const isString = (value: any): value is string => { + return typeof value === 'string'; +}; diff --git a/packages/oas/openapi-typescript-codegen/src/templates/core/functions/isStringWithValue.hbs b/packages/oas/openapi-typescript-codegen/src/templates/core/functions/isStringWithValue.hbs new file mode 100644 index 0000000000..b1659067c5 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/core/functions/isStringWithValue.hbs @@ -0,0 +1,3 @@ +const isStringWithValue = (value: any): value is string => { + return isString(value) && value !== ''; +}; diff --git a/packages/oas/openapi-typescript-codegen/src/templates/core/functions/isSuccess.hbs b/packages/oas/openapi-typescript-codegen/src/templates/core/functions/isSuccess.hbs new file mode 100644 index 0000000000..318af1241c --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/core/functions/isSuccess.hbs @@ -0,0 +1,3 @@ +const isSuccess = (status: number): boolean => { + return status >= 200 && status < 300; +}; diff --git a/packages/oas/openapi-typescript-codegen/src/templates/core/functions/resolve.hbs b/packages/oas/openapi-typescript-codegen/src/templates/core/functions/resolve.hbs new file mode 100644 index 0000000000..40dbed816d --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/core/functions/resolve.hbs @@ -0,0 +1,8 @@ +type Resolver = (options: ApiRequestOptions) => Promise; + +const resolve = async (options: ApiRequestOptions, resolver?: T | Resolver): Promise => { + if (typeof resolver === 'function') { + return (resolver as Resolver)(options); + } + return resolver; +}; diff --git a/packages/oas/openapi-typescript-codegen/src/templates/core/node/getHeaders.hbs b/packages/oas/openapi-typescript-codegen/src/templates/core/node/getHeaders.hbs new file mode 100644 index 0000000000..80f2cbcce7 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/core/node/getHeaders.hbs @@ -0,0 +1,40 @@ +const getHeaders = async (config: OpenAPIConfig, options: ApiRequestOptions): Promise => { + const token = await resolve(options, config.TOKEN); + const username = await resolve(options, config.USERNAME); + const password = await resolve(options, config.PASSWORD); + const additionalHeaders = await resolve(options, config.HEADERS); + + const headers = Object.entries({ + Accept: 'application/json', + ...additionalHeaders, + ...options.headers, + }) + .filter(([_, value]) => isDefined(value)) + .reduce((headers, [key, value]) => ({ + ...headers, + [key]: String(value), + }), {} as Record); + + if (isStringWithValue(token)) { + headers['Authorization'] = `Bearer ${token}`; + } + + if (isStringWithValue(username) && isStringWithValue(password)) { + const credentials = base64(`${username}:${password}`); + headers['Authorization'] = `Basic ${credentials}`; + } + + if (options.body) { + if (options.mediaType) { + headers['Content-Type'] = options.mediaType; + } else if (isBlob(options.body)) { + headers['Content-Type'] = 'application/octet-stream'; + } else if (isString(options.body)) { + headers['Content-Type'] = 'text/plain'; + } else if (!isFormData(options.body)) { + headers['Content-Type'] = 'application/json'; + } + } + + return new Headers(headers); +}; diff --git a/packages/oas/openapi-typescript-codegen/src/templates/core/node/getRequestBody.hbs b/packages/oas/openapi-typescript-codegen/src/templates/core/node/getRequestBody.hbs new file mode 100644 index 0000000000..d04d210cd6 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/core/node/getRequestBody.hbs @@ -0,0 +1,12 @@ +const getRequestBody = (options: ApiRequestOptions): any => { + if (options.body !== undefined) { + if (options.mediaType?.includes('/json')) { + return JSON.stringify(options.body) + } else if (isString(options.body) || isBlob(options.body) || isFormData(options.body)) { + return options.body as any; + } else { + return JSON.stringify(options.body); + } + } + return undefined; +}; diff --git a/packages/oas/openapi-typescript-codegen/src/templates/core/node/getResponseBody.hbs b/packages/oas/openapi-typescript-codegen/src/templates/core/node/getResponseBody.hbs new file mode 100644 index 0000000000..556c1f03f8 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/core/node/getResponseBody.hbs @@ -0,0 +1,19 @@ +const getResponseBody = async (response: Response): Promise => { + if (response.status !== 204) { + try { + const contentType = response.headers.get('Content-Type'); + if (contentType) { + const jsonTypes = ['application/json', 'application/problem+json'] + const isJSON = jsonTypes.some(type => contentType.toLowerCase().startsWith(type)); + if (isJSON) { + return await response.json(); + } else { + return await response.text(); + } + } + } catch (error) { + console.error(error); + } + } + return undefined; +}; diff --git a/packages/oas/openapi-typescript-codegen/src/templates/core/node/getResponseHeader.hbs b/packages/oas/openapi-typescript-codegen/src/templates/core/node/getResponseHeader.hbs new file mode 100644 index 0000000000..531fb84f1f --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/core/node/getResponseHeader.hbs @@ -0,0 +1,9 @@ +const getResponseHeader = (response: Response, responseHeader?: string): string | undefined => { + if (responseHeader) { + const content = response.headers.get(responseHeader); + if (isString(content)) { + return content; + } + } + return undefined; +}; diff --git a/packages/oas/openapi-typescript-codegen/src/templates/core/node/request.hbs b/packages/oas/openapi-typescript-codegen/src/templates/core/node/request.hbs new file mode 100644 index 0000000000..8e6f6110e1 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/core/node/request.hbs @@ -0,0 +1,99 @@ +{{>header}} + +import FormData from 'form-data'; +import fetch, { Headers } from 'node-fetch'; +import type { RequestInit, Response } from 'node-fetch'; +import type { AbortSignal } from 'node-fetch/externals'; + +import { ApiError } from './ApiError'; +import type { ApiRequestOptions } from './ApiRequestOptions'; +import type { ApiResult } from './ApiResult'; +import { CancelablePromise } from './CancelablePromise'; +import type { OnCancel } from './CancelablePromise'; +import type { OpenAPIConfig } from './OpenAPI'; + +{{>functions/isDefined}} + + +{{>functions/isString}} + + +{{>functions/isStringWithValue}} + + +{{>functions/isBlob}} + + +{{>functions/isFormData}} + + +{{>functions/base64}} + + +{{>functions/getQueryString}} + + +{{>functions/getUrl}} + + +{{>functions/getFormData}} + + +{{>functions/resolve}} + + +{{>node/getHeaders}} + + +{{>node/getRequestBody}} + + +{{>node/sendRequest}} + + +{{>node/getResponseHeader}} + + +{{>node/getResponseBody}} + + +{{>functions/catchErrorCodes}} + + +/** + * Request method + * @param config The OpenAPI configuration object + * @param options The request options from the service + * @returns CancelablePromise + * @throws ApiError + */ +export const request = (config: OpenAPIConfig, options: ApiRequestOptions): CancelablePromise => { + return new CancelablePromise(async (resolve, reject, onCancel) => { + try { + const url = getUrl(config, options); + const formData = getFormData(options); + const body = getRequestBody(options); + const headers = await getHeaders(config, options); + + if (!onCancel.isCancelled) { + const response = await sendRequest(options, url, body, formData, headers, onCancel); + const responseBody = await getResponseBody(response); + const responseHeader = getResponseHeader(response, options.responseHeader); + + const result: ApiResult = { + url, + ok: response.ok, + status: response.status, + statusText: response.statusText, + body: responseHeader ?? responseBody, + }; + + catchErrorCodes(options, result); + + resolve(result.body); + } + } catch (error) { + reject(error); + } + }); +}; diff --git a/packages/oas/openapi-typescript-codegen/src/templates/core/node/sendRequest.hbs b/packages/oas/openapi-typescript-codegen/src/templates/core/node/sendRequest.hbs new file mode 100644 index 0000000000..a2ebf86d44 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/core/node/sendRequest.hbs @@ -0,0 +1,21 @@ +export const sendRequest = async ( + options: ApiRequestOptions, + url: string, + body: any, + formData: FormData | undefined, + headers: Headers, + onCancel: OnCancel +): Promise => { + const controller = new AbortController(); + + const request: RequestInit = { + headers, + method: options.method, + body: body ?? formData, + signal: controller.signal as AbortSignal, + }; + + onCancel(() => controller.abort()); + + return await fetch(url, request); +}; diff --git a/packages/oas/openapi-typescript-codegen/src/templates/core/request.hbs b/packages/oas/openapi-typescript-codegen/src/templates/core/request.hbs new file mode 100644 index 0000000000..4c472b500d --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/core/request.hbs @@ -0,0 +1,4 @@ +{{~#equals @root.httpClient 'fetch'}}{{>fetch/request}}{{/equals~}} +{{~#equals @root.httpClient 'xhr'}}{{>xhr/request}}{{/equals~}} +{{~#equals @root.httpClient 'axios'}}{{>axios/request}}{{/equals~}} +{{~#equals @root.httpClient 'node'}}{{>node/request}}{{/equals~}} diff --git a/packages/oas/openapi-typescript-codegen/src/templates/core/xhr/getHeaders.hbs b/packages/oas/openapi-typescript-codegen/src/templates/core/xhr/getHeaders.hbs new file mode 100644 index 0000000000..41a65005c0 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/core/xhr/getHeaders.hbs @@ -0,0 +1,40 @@ +const getHeaders = async (config: OpenAPIConfig, options: ApiRequestOptions): Promise => { + const token = await resolve(options, config.TOKEN); + const username = await resolve(options, config.USERNAME); + const password = await resolve(options, config.PASSWORD); + const additionalHeaders = await resolve(options, config.HEADERS); + + const headers = Object.entries({ + Accept: 'application/json', + ...additionalHeaders, + ...options.headers, + }) + .filter(([_, value]) => isDefined(value)) + .reduce((headers, [key, value]) => ({ + ...headers, + [key]: String(value), + }), {} as Record); + + if (isStringWithValue(token)) { + headers['Authorization'] = `Bearer ${token}`; + } + + if (isStringWithValue(username) && isStringWithValue(password)) { + const credentials = base64(`${username}:${password}`); + headers['Authorization'] = `Basic ${credentials}`; + } + + if (options.body) { + if (options.mediaType) { + headers['Content-Type'] = options.mediaType; + } else if (isBlob(options.body)) { + headers['Content-Type'] = options.body.type || 'application/octet-stream'; + } else if (isString(options.body)) { + headers['Content-Type'] = 'text/plain'; + } else if (!isFormData(options.body)) { + headers['Content-Type'] = 'application/json'; + } + } + + return new Headers(headers); +}; diff --git a/packages/oas/openapi-typescript-codegen/src/templates/core/xhr/getRequestBody.hbs b/packages/oas/openapi-typescript-codegen/src/templates/core/xhr/getRequestBody.hbs new file mode 100644 index 0000000000..b2fc7e21cd --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/core/xhr/getRequestBody.hbs @@ -0,0 +1,12 @@ +const getRequestBody = (options: ApiRequestOptions): any => { + if (options.body !== undefined) { + if (options.mediaType?.includes('/json')) { + return JSON.stringify(options.body) + } else if (isString(options.body) || isBlob(options.body) || isFormData(options.body)) { + return options.body; + } else { + return JSON.stringify(options.body); + } + } + return undefined; +}; diff --git a/packages/oas/openapi-typescript-codegen/src/templates/core/xhr/getResponseBody.hbs b/packages/oas/openapi-typescript-codegen/src/templates/core/xhr/getResponseBody.hbs new file mode 100644 index 0000000000..84a662ef7c --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/core/xhr/getResponseBody.hbs @@ -0,0 +1,19 @@ +const getResponseBody = (xhr: XMLHttpRequest): any => { + if (xhr.status !== 204) { + try { + const contentType = xhr.getResponseHeader('Content-Type'); + if (contentType) { + const jsonTypes = ['application/json', 'application/problem+json'] + const isJSON = jsonTypes.some(type => contentType.toLowerCase().startsWith(type)); + if (isJSON) { + return JSON.parse(xhr.responseText); + } else { + return xhr.responseText; + } + } + } catch (error) { + console.error(error); + } + } + return undefined; +}; diff --git a/packages/oas/openapi-typescript-codegen/src/templates/core/xhr/getResponseHeader.hbs b/packages/oas/openapi-typescript-codegen/src/templates/core/xhr/getResponseHeader.hbs new file mode 100644 index 0000000000..b117d09ddc --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/core/xhr/getResponseHeader.hbs @@ -0,0 +1,9 @@ +const getResponseHeader = (xhr: XMLHttpRequest, responseHeader?: string): string | undefined => { + if (responseHeader) { + const content = xhr.getResponseHeader(responseHeader); + if (isString(content)) { + return content; + } + } + return undefined; +}; diff --git a/packages/oas/openapi-typescript-codegen/src/templates/core/xhr/request.hbs b/packages/oas/openapi-typescript-codegen/src/templates/core/xhr/request.hbs new file mode 100644 index 0000000000..47f92870b0 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/core/xhr/request.hbs @@ -0,0 +1,97 @@ +{{>header}} + +import { ApiError } from './ApiError'; +import type { ApiRequestOptions } from './ApiRequestOptions'; +import type { ApiResult } from './ApiResult'; +import { CancelablePromise } from './CancelablePromise'; +import type { OnCancel } from './CancelablePromise'; +import type { OpenAPIConfig } from './OpenAPI'; + +{{>functions/isDefined}} + + +{{>functions/isString}} + + +{{>functions/isStringWithValue}} + + +{{>functions/isBlob}} + + +{{>functions/isFormData}} + + +{{>functions/isSuccess}} + + +{{>functions/base64}} + + +{{>functions/getQueryString}} + + +{{>functions/getUrl}} + + +{{>functions/getFormData}} + + +{{>functions/resolve}} + + +{{>fetch/getHeaders}} + + +{{>xhr/getRequestBody}} + + +{{>xhr/sendRequest}} + + +{{>xhr/getResponseHeader}} + + +{{>xhr/getResponseBody}} + + +{{>functions/catchErrorCodes}} + + +/** + * Request method + * @param config The OpenAPI configuration object + * @param options The request options from the service + * @returns CancelablePromise + * @throws ApiError + */ +export const request = (config: OpenAPIConfig, options: ApiRequestOptions): CancelablePromise => { + return new CancelablePromise(async (resolve, reject, onCancel) => { + try { + const url = getUrl(config, options); + const formData = getFormData(options); + const body = getRequestBody(options); + const headers = await getHeaders(config, options); + + if (!onCancel.isCancelled) { + const response = await sendRequest(config, options, url, body, formData, headers, onCancel); + const responseBody = getResponseBody(response); + const responseHeader = getResponseHeader(response, options.responseHeader); + + const result: ApiResult = { + url, + ok: isSuccess(response.status), + status: response.status, + statusText: response.statusText, + body: responseHeader ?? responseBody, + }; + + catchErrorCodes(options, result); + + resolve(result.body); + } + } catch (error) { + reject(error); + } + }); +}; diff --git a/packages/oas/openapi-typescript-codegen/src/templates/core/xhr/sendRequest.hbs b/packages/oas/openapi-typescript-codegen/src/templates/core/xhr/sendRequest.hbs new file mode 100644 index 0000000000..0badf8daaa --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/core/xhr/sendRequest.hbs @@ -0,0 +1,26 @@ +export const sendRequest = async ( + config: OpenAPIConfig, + options: ApiRequestOptions, + url: string, + body: any, + formData: FormData | undefined, + headers: Headers, + onCancel: OnCancel +): Promise => { + const xhr = new XMLHttpRequest(); + xhr.open(options.method, url, true); + xhr.withCredentials = config.WITH_CREDENTIALS; + + headers.forEach((value, key) => { + xhr.setRequestHeader(key, value); + }); + + return new Promise((resolve, reject) => { + xhr.onload = () => resolve(xhr); + xhr.onabort = () => reject(new Error('Request aborted')); + xhr.onerror = () => reject(new Error('Network error')); + xhr.send(body ?? formData); + + onCancel(() => xhr.abort()); + }); +}; diff --git a/packages/oas/openapi-typescript-codegen/src/templates/exportHook.hbs b/packages/oas/openapi-typescript-codegen/src/templates/exportHook.hbs new file mode 100644 index 0000000000..099ce1a278 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/exportHook.hbs @@ -0,0 +1,54 @@ +{{>header}} + +import { useQuery, useMutation, useQueryClient } from 'react-query'; +import { UseQueryOptionsWrapper, UseMutationOptionsWrapper} from '../core/HookUtils'; +import { use{{{pascalCase clientName}}} } from '../use{{pascalCase clientName}}'; +{{#if imports}} + {{#each imports}} + {{#if @root.packageNames.models}} +import type { {{{this}}} } from '{{{@root.packageNames.models}}}'; + {{else}} +import type { {{{this}}} } from '../models/{{{this}}}'; + {{/if}} + {{/each}} +{{/if}} + +const { client } = use{{{pascalCase clientName}}}() + +{{#each operations}} + export const use{{{service}}}{{{pascalCase name}}} = ( + {{~#if parameters}}{{~>parameters~}},{{/if}} + {{#equals method 'GET'}} + options: UseQueryOptionsWrapper>> = {} + {{else}} + options: UseMutationOptionsWrapper>, unknown, + {{~#if parametersBody}} {{parametersBody.type}}{{else}} void{{/if~}} + > = {} + {{/equals}} + ) => { + {{#equals method 'GET'}} + const { data, ...rest } = useQuery>>( + ['{{camelCase service}}', '{{ camelCase name }}'{{#if parameters}}, {{>parametersUntyped}}{{/if}}], + () => client.{{camelCase service}}.{{name}}({{#if parameters}}{{>parametersUntyped}}{{/if}}), + options + ); + return { ...data, ...rest } as const + {{else}} + if (!options?.onSuccess) { + const queryClient = useQueryClient() + options.onSuccess = async () => { + await queryClient.invalidateQueries('{{camelCase service}}') + } + } + return useMutation>, unknown, + {{~#if parametersBody}} {{parametersBody.type}}{{else}} void{{/if~}} + >( + ['{{camelCase service}}', '{{ camelCase name }}'{{#if parameters}}, {{>parametersUntyped}}{{/if}}], + () => client.{{camelCase service}}.{{name}}({{#if parameters}}{{>parametersUntyped}}{{/if}}), + options + ); + {{/equals}} + }; + +{{/each}} + diff --git a/packages/oas/openapi-typescript-codegen/src/templates/exportModel.hbs b/packages/oas/openapi-typescript-codegen/src/templates/exportModel.hbs new file mode 100644 index 0000000000..7ddb2c3e8f --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/exportModel.hbs @@ -0,0 +1,26 @@ +{{>header}} + +{{#if imports}} + +{{#each imports}} +import type { {{{this}}} } from './{{{this}}}'; +{{/each}} +{{/if}} + +{{#equals export 'interface'}} +{{>exportInterface}} +{{else equals export 'one-of'}} +{{>exportComposition}} +{{else equals export 'any-of'}} +{{>exportComposition}} +{{else equals export 'all-of'}} +{{>exportComposition}} +{{else equals export 'enum'}} +{{#if @root.useUnionTypes}} +{{>exportType}} +{{else}} +{{>exportEnum}} +{{/if}} +{{else}} +{{>exportType}} +{{/equals}} diff --git a/packages/oas/openapi-typescript-codegen/src/templates/exportSchema.hbs b/packages/oas/openapi-typescript-codegen/src/templates/exportSchema.hbs new file mode 100644 index 0000000000..f54c25fd9d --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/exportSchema.hbs @@ -0,0 +1,3 @@ +{{>header}} + +export const ${{{name}}} = {{>schema}} as const; diff --git a/packages/oas/openapi-typescript-codegen/src/templates/exportService.hbs b/packages/oas/openapi-typescript-codegen/src/templates/exportService.hbs new file mode 100644 index 0000000000..708a29d25a --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/exportService.hbs @@ -0,0 +1,119 @@ +{{>header}} + +{{#if imports}} + import { + {{#each imports}} + {{{this}}}, + {{/each}} + } {{#if @root.packageNames.models~}} + from '{{{@root.packageNames.models}}}'; + {{else~}} + from '../models'; + {{/if}} +{{/if}} +import type { CancelablePromise } from '../core/CancelablePromise'; +{{#if @root.exportClient}} +import type { BaseHttpRequest } from '../core/BaseHttpRequest'; +{{else}} +import { OpenAPI } from '../core/OpenAPI'; +import { request as __request } from '../core/request'; +{{/if}} + +export class {{{name}}}{{{@root.postfix}}} { + {{#if @root.exportClient}} + + constructor(public readonly httpRequest: BaseHttpRequest) {} + {{/if}} + + {{#each operations}} + /** + {{#if deprecated}} + * @deprecated + {{/if}} + {{#if operationId}} + * {{{escapeComment operationId}}} + {{/if}} + {{#if summary}} + * {{{escapeComment summary}}} + {{/if}} + {{#if description}} + * {{{escapeComment description}}} + {{/if}} + {{#unless @root.useOptions}} + {{#if parameters}} + {{#each parameters}} + * @param {{{name}}} {{#if description}}{{{escapeComment description}}}{{/if}} + {{/each}} + {{/if}} + {{/unless}} + {{#each results}} + * @returns {{{type}}} {{#if description}}{{{escapeComment description}}}{{/if}} + {{/each}} + * @throws ApiError + */ + {{#if @root.exportClient}} + public {{{name}}}( + {{~#if parameters}}{{~>parameters~}},{{/if}} + customHeaders: Record = {} + ): CancelablePromise<{{>result}}> { + return this.httpRequest.request({ + {{else}} + public static {{{name}}}( + {{#if parameters}}{{~>parameters~}},{{/if}} + customHeaders: Record = {} + ): CancelablePromise<{{>result}}> { + return __request(OpenAPI, { + {{/if}} + method: '{{{method}}}', + url: '{{{path}}}', + {{#if parametersPath}} + path: { + {{#each parametersPath}} + '{{{prop}}}': {{{name}}}, + {{/each}} + }, + {{/if}} + {{#if parametersCookie}} + cookies: { + {{#each parametersCookie}} + '{{{prop}}}': {{{name}}}, + {{/each}} + }, + {{/if}} + headers: customHeaders, + {{#if parametersQuery}} + query: queryParams, + {{/if}} + {{#if parametersForm}} + formData: { + {{#each parametersForm}} + '{{{prop}}}': {{{name}}}, + {{/each}} + }, + {{/if}} + {{#if parametersBody}} + {{#equals parametersBody.in 'formData'}} + formData: {{{parametersBody.name}}}, + {{/equals}} + {{#equals parametersBody.in 'body'}} + body: {{{parametersBody.name}}}, + {{/equals}} + {{#if parametersBody.mediaType}} + mediaType: '{{{parametersBody.mediaType}}}', + {{/if}} + {{/if}} + {{#if responseHeader}} + responseHeader: '{{{responseHeader}}}', + {{/if}} + {{#if errors}} + errors: { + {{#each errors}} + {{{code}}}: `{{{escapeDescription description}}}`, + {{/each}} + }, + {{/if}} + }); + } + + {{/each}} +} diff --git a/packages/oas/openapi-typescript-codegen/src/templates/indexes/index.hbs b/packages/oas/openapi-typescript-codegen/src/templates/indexes/index.hbs new file mode 100644 index 0000000000..73d3905707 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/indexes/index.hbs @@ -0,0 +1,38 @@ +{{>header}} + +{{#if @root.exportClient}} +export { {{{clientName}}} } from './{{{clientName}}}'; +{{#if @root.exportHooks}} +export { use{{{pascalCase clientName}}}, {{{pascalCase clientName}}}Provider } from './use{{{pascalCase clientName}}}'; +{{/if}} +{{/if}} + +{{#if @root.exportCore}} +export { ApiError } from './core/ApiError'; +{{#if @root.exportClient}} +export { BaseHttpRequest } from './core/BaseHttpRequest'; +{{/if}} +export { CancelablePromise, CancelError } from './core/CancelablePromise'; +export { OpenAPI } from './core/OpenAPI'; +export type { OpenAPIConfig } from './core/OpenAPI'; +{{/if}} + +{{#if @root.exportModels}} +{{#if models}} +export * from './models'; +{{/if}} +{{/if}} + +{{#if @root.exportSchemas}} +{{#if models}} +{{#each models}} +export { ${{{name}}} } from './schemas/${{{name}}}'; +{{/each}} +{{/if}} +{{/if}} + +{{#if @root.exportServices}} +{{#if services}} +export * from './services'; +{{/if}} +{{/if}} diff --git a/packages/oas/openapi-typescript-codegen/src/templates/indexes/indexHooks.hbs b/packages/oas/openapi-typescript-codegen/src/templates/indexes/indexHooks.hbs new file mode 100644 index 0000000000..e67d7578a3 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/indexes/indexHooks.hbs @@ -0,0 +1,7 @@ +{{>header}} + +{{#if services}} +{{#each services}} +export * from './use{{{pascalCase name}}}'; +{{/each}} +{{/if}} diff --git a/packages/oas/openapi-typescript-codegen/src/templates/indexes/indexModels.hbs b/packages/oas/openapi-typescript-codegen/src/templates/indexes/indexModels.hbs new file mode 100644 index 0000000000..6bd6e02576 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/indexes/indexModels.hbs @@ -0,0 +1,17 @@ +{{>header}} + +{{#if @root.exportModels}} +{{#if models}} +{{#each models}} +{{#if @root.useUnionTypes}} +export type { {{{name}}}{{#if @root.postfixModels}} as {{{name}}}{{{@root.postfixModels}}}{{/if}} } from './{{{name}}}'; +{{else if enum}} +export { {{{name}}}{{#if @root.postfixModels}} as {{{name}}}{{{@root.postfixModels}}}{{/if}} } from './{{{name}}}'; +{{else if enums}} +export { {{{name}}}{{#if @root.postfixModels}} as {{{name}}}{{{@root.postfixModels}}}{{/if}} } from './{{{name}}}'; +{{else}} +export type { {{{name}}}{{#if @root.postfixModels}} as {{{name}}}{{{@root.postfixModels}}}{{/if}} } from './{{{name}}}'; +{{/if}} +{{/each}} +{{/if}} +{{/if}} diff --git a/packages/oas/openapi-typescript-codegen/src/templates/indexes/indexServices.hbs b/packages/oas/openapi-typescript-codegen/src/templates/indexes/indexServices.hbs new file mode 100644 index 0000000000..2eac59892d --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/indexes/indexServices.hbs @@ -0,0 +1,7 @@ +{{>header}} + +{{#if services}} +{{#each services}} +export * from './{{{name}}}{{{@root.postfixServices}}}'; +{{/each}} +{{/if}} diff --git a/packages/oas/openapi-typescript-codegen/src/templates/partials/base.hbs b/packages/oas/openapi-typescript-codegen/src/templates/partials/base.hbs new file mode 100644 index 0000000000..0981843097 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/partials/base.hbs @@ -0,0 +1,8 @@ +{{~#equals base 'binary'~}} +{{~#equals @root.httpClient 'fetch'}}Blob{{/equals~}} +{{~#equals @root.httpClient 'xhr'}}Blob{{/equals~}} +{{~#equals @root.httpClient 'axios'}}Blob{{/equals~}} +{{~#equals @root.httpClient 'node'}}Blob{{/equals~}} +{{~else~}} +{{{base}}} +{{~/equals~}} diff --git a/packages/oas/openapi-typescript-codegen/src/templates/partials/exportComposition.hbs b/packages/oas/openapi-typescript-codegen/src/templates/partials/exportComposition.hbs new file mode 100644 index 0000000000..15bf6f65c4 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/partials/exportComposition.hbs @@ -0,0 +1,38 @@ +{{#ifdef description deprecated}} +/** +{{#if description}} + * {{{escapeComment description}}} +{{/if}} +{{#if deprecated}} + * @deprecated +{{/if}} + */ +{{/ifdef}} +export type {{{name}}} = {{>type parent=name}}; +{{#if enums}} +{{#unless @root.useUnionTypes}} + +export namespace {{{name}}} { + + {{#each enums}} + {{#ifdef description deprecated}} + /** + {{#if description}} + * {{{escapeComment description}}} + {{/if}} + {{#if deprecated}} + * @deprecated + {{/if}} + */ + {{/ifdef}} + export enum {{{name}}} { + {{#each enum}} + {{{name}}} = {{{value}}}, + {{/each}} + } + + {{/each}} + +} +{{/unless}} +{{/if}} diff --git a/packages/oas/openapi-typescript-codegen/src/templates/partials/exportEnum.hbs b/packages/oas/openapi-typescript-codegen/src/templates/partials/exportEnum.hbs new file mode 100644 index 0000000000..f47622ef9e --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/partials/exportEnum.hbs @@ -0,0 +1,24 @@ +{{#ifdef description deprecated}} +/** +{{#if description}} + * {{{escapeComment description}}} +{{/if}} +{{#if deprecated}} + * @deprecated +{{/if}} + */ +{{/ifdef}} +export enum {{{name}}} { + {{#each enum}} + {{#if description}} + /** + * {{{escapeComment description}}} + */ + {{/if}} + {{#containsSpaces name}} + '{{{name}}}' = {{{value}}}, + {{else}} + {{{name}}} = {{{value}}}, + {{/containsSpaces}} + {{/each}} +} diff --git a/packages/oas/openapi-typescript-codegen/src/templates/partials/exportInterface.hbs b/packages/oas/openapi-typescript-codegen/src/templates/partials/exportInterface.hbs new file mode 100644 index 0000000000..b61e26441b --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/partials/exportInterface.hbs @@ -0,0 +1,47 @@ +{{#ifdef description deprecated}} +/** +{{#if description}} + * {{{escapeComment description}}} +{{/if}} +{{#if deprecated}} + * @deprecated +{{/if}} + */ +{{/ifdef}} +export type {{{name}}} = { + {{#each properties}} + {{#ifdef description deprecated}} + /** + {{#if description}} + * {{{escapeComment description}}} + {{/if}} + {{#if deprecated}} + * @deprecated + {{/if}} + */ + {{/ifdef}} + {{>isReadOnly}}{{{name}}}{{>isRequired}}: {{>type parent=../name}}; + {{/each}} +}; +{{#if enums}} +{{#unless @root.useUnionTypes}} + +export namespace {{{name}}} { + + {{#each enums}} + {{#if description}} + /** + * {{{escapeComment description}}} + */ + {{/if}} + export enum {{{name}}} { + {{#each enum}} + {{{name}}} = {{{value}}}, + {{/each}} + } + + {{/each}} + +} +{{/unless}} +{{/if}} diff --git a/packages/oas/openapi-typescript-codegen/src/templates/partials/exportType.hbs b/packages/oas/openapi-typescript-codegen/src/templates/partials/exportType.hbs new file mode 100644 index 0000000000..b57790a0ba --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/partials/exportType.hbs @@ -0,0 +1,11 @@ +{{#ifdef description deprecated}} +/** +{{#if description}} + * {{{escapeComment description}}} +{{/if}} +{{#if deprecated}} + * @deprecated +{{/if}} + */ +{{/ifdef}} +export type {{{name}}} = {{>type}}; diff --git a/packages/oas/openapi-typescript-codegen/src/templates/partials/header.hbs b/packages/oas/openapi-typescript-codegen/src/templates/partials/header.hbs new file mode 100644 index 0000000000..d592379e7b --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/partials/header.hbs @@ -0,0 +1,3 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ diff --git a/packages/oas/openapi-typescript-codegen/src/templates/partials/isNullable.hbs b/packages/oas/openapi-typescript-codegen/src/templates/partials/isNullable.hbs new file mode 100644 index 0000000000..7ef73e3d00 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/partials/isNullable.hbs @@ -0,0 +1 @@ +{{#if isNullable}} | null{{/if}} diff --git a/packages/oas/openapi-typescript-codegen/src/templates/partials/isReadOnly.hbs b/packages/oas/openapi-typescript-codegen/src/templates/partials/isReadOnly.hbs new file mode 100644 index 0000000000..7ab3bdbf6b --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/partials/isReadOnly.hbs @@ -0,0 +1 @@ +{{#if isReadOnly}}readonly {{/if}} diff --git a/packages/oas/openapi-typescript-codegen/src/templates/partials/isRequired.hbs b/packages/oas/openapi-typescript-codegen/src/templates/partials/isRequired.hbs new file mode 100644 index 0000000000..b829272b96 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/partials/isRequired.hbs @@ -0,0 +1,5 @@ +{{~#if @root.useOptions~}} +{{#unless isRequired}}?{{else if default}}?{{/unless}} +{{~else~}} +{{#unless isRequired}}{{#unless default}}?{{/unless}}{{/unless}} +{{~/if~}} diff --git a/packages/oas/openapi-typescript-codegen/src/templates/partials/parameters.hbs b/packages/oas/openapi-typescript-codegen/src/templates/partials/parameters.hbs new file mode 100644 index 0000000000..964d55b2a2 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/partials/parameters.hbs @@ -0,0 +1,38 @@ +{{~#if parameters}} + {{~#if parametersPath}} + + {{#each parametersPath}} + {{{name}}}{{>isRequired}}: {{>type}}{{#if default}} = {{{default}}}{{/if}}{{#unless @last}}, + {{/unless}} + {{~/each}} + {{~/if}} + {{~#if parametersBody}} + {{~#if parametersPath}},{{/if}} + {{#with parametersBody}} + {{{name}}}: {{>type}} + {{~/with}} + {{~#if parametersQuery}},{{/if}} + {{~/if}} + {{~#if parametersQuery}} + {{~#if parametersPath}}{{#unless parametersBody}},{{/unless}}{{/if}} + {{#if codegen.queryParams}} + queryParams: {{codegen.queryParams}} + {{~else~}} + queryParams: { + {{#each parametersQuery}} + {{#ifdef description deprecated}} + /** + {{#if description}} + * {{{escapeComment description}}} + {{/if}} + {{#if deprecated}} + * @deprecated + {{/if}} + */ + {{/ifdef}} + {{{prop}}}{{>isRequired}}: {{>type}}, + {{/each}} + } + {{~/if}} + {{~/if}} +{{~/if}} diff --git a/packages/oas/openapi-typescript-codegen/src/templates/partials/parametersUntyped.hbs b/packages/oas/openapi-typescript-codegen/src/templates/partials/parametersUntyped.hbs new file mode 100644 index 0000000000..4523aed472 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/partials/parametersUntyped.hbs @@ -0,0 +1,18 @@ +{{~#if parameters}} + {{~#if parametersPath}} + {{~#each parametersPath}} + {{~{name}}}{{#unless @last}},{{/unless}} + {{~/each}} + {{~/if}} + {{~#if parametersBody}} + {{~#if parametersPath}},{{/if}} + {{~#with parametersBody}} + {{~{name}}} + {{~/with}} + {{~#if parametersQuery}},{{/if}} + {{~/if}} + {{~#if parametersQuery}} + {{~#if parametersPath}}{{#unless parametersBody}},{{/unless}}{{/if~}} + queryParams + {{~/if}} +{{~/if}} diff --git a/packages/oas/openapi-typescript-codegen/src/templates/partials/result.hbs b/packages/oas/openapi-typescript-codegen/src/templates/partials/result.hbs new file mode 100644 index 0000000000..c8f4770fdc --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/partials/result.hbs @@ -0,0 +1,5 @@ +{{~#if results~}} +{{#each results}}{{>type}}{{#unless @last}} | {{/unless}}{{/each}} +{{~else~}} +void +{{~/if~}} diff --git a/packages/oas/openapi-typescript-codegen/src/templates/partials/schema.hbs b/packages/oas/openapi-typescript-codegen/src/templates/partials/schema.hbs new file mode 100644 index 0000000000..14b9156b05 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/partials/schema.hbs @@ -0,0 +1,17 @@ +{{#equals export 'interface'}} +{{>schemaInterface}} +{{else equals export 'enum'}} +{{>schemaEnum}} +{{else equals export 'array'}} +{{>schemaArray}} +{{else equals export 'dictionary'}} +{{>schemaDictionary}} +{{else equals export 'any-of'}} +{{>schemaComposition}} +{{else equals export 'all-of'}} +{{>schemaComposition}} +{{else equals export 'one-of'}} +{{>schemaComposition}} +{{else}} +{{>schemaGeneric}} +{{/equals}} diff --git a/packages/oas/openapi-typescript-codegen/src/templates/partials/schemaArray.hbs b/packages/oas/openapi-typescript-codegen/src/templates/partials/schemaArray.hbs new file mode 100644 index 0000000000..44871265b8 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/partials/schemaArray.hbs @@ -0,0 +1,19 @@ +{ + type: 'array', +{{#if link}} + contains: {{>schema link}}, +{{else}} + contains: { + type: '{{{base}}}', + }, +{{/if}} +{{#if isReadOnly}} + isReadOnly: {{{isReadOnly}}}, +{{/if}} +{{#if isRequired}} + isRequired: {{{isRequired}}}, +{{/if}} +{{#if isNullable}} + isNullable: {{{isNullable}}}, +{{/if}} +} diff --git a/packages/oas/openapi-typescript-codegen/src/templates/partials/schemaComposition.hbs b/packages/oas/openapi-typescript-codegen/src/templates/partials/schemaComposition.hbs new file mode 100644 index 0000000000..050f6696d5 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/partials/schemaComposition.hbs @@ -0,0 +1,16 @@ +{ + type: '{{export}}', +{{#if description}} + description: `{{{escapeDescription description}}}`, +{{/if}} + contains: [{{#each properties}}{{>schema}}{{#unless @last}}, {{/unless}}{{/each}}], +{{#if isReadOnly}} + isReadOnly: {{{isReadOnly}}}, +{{/if}} +{{#if isRequired}} + isRequired: {{{isRequired}}}, +{{/if}} +{{#if isNullable}} + isNullable: {{{isNullable}}}, +{{/if}} +} diff --git a/packages/oas/openapi-typescript-codegen/src/templates/partials/schemaDictionary.hbs b/packages/oas/openapi-typescript-codegen/src/templates/partials/schemaDictionary.hbs new file mode 100644 index 0000000000..1e755f7af9 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/partials/schemaDictionary.hbs @@ -0,0 +1,19 @@ +{ + type: 'dictionary', +{{#if link}} + contains: {{>schema link}}, +{{else}} + contains: { + type: '{{{base}}}', + }, +{{/if}} +{{#if isReadOnly}} + isReadOnly: {{{isReadOnly}}}, +{{/if}} +{{#if isRequired}} + isRequired: {{{isRequired}}}, +{{/if}} +{{#if isNullable}} + isNullable: {{{isNullable}}}, +{{/if}} +} diff --git a/packages/oas/openapi-typescript-codegen/src/templates/partials/schemaEnum.hbs b/packages/oas/openapi-typescript-codegen/src/templates/partials/schemaEnum.hbs new file mode 100644 index 0000000000..eaba3fc5f8 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/partials/schemaEnum.hbs @@ -0,0 +1,12 @@ +{ + type: 'Enum', +{{#if isReadOnly}} + isReadOnly: {{{isReadOnly}}}, +{{/if}} +{{#if isRequired}} + isRequired: {{{isRequired}}}, +{{/if}} +{{#if isNullable}} + isNullable: {{{isNullable}}}, +{{/if}} +} diff --git a/packages/oas/openapi-typescript-codegen/src/templates/partials/schemaGeneric.hbs b/packages/oas/openapi-typescript-codegen/src/templates/partials/schemaGeneric.hbs new file mode 100644 index 0000000000..23580a6734 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/partials/schemaGeneric.hbs @@ -0,0 +1,59 @@ +{ +{{#if type}} + type: '{{{type}}}', +{{/if}} +{{#if description}} + description: `{{{escapeDescription description}}}`, +{{/if}} +{{#if isReadOnly}} + isReadOnly: {{{isReadOnly}}}, +{{/if}} +{{#if isRequired}} + isRequired: {{{isRequired}}}, +{{/if}} +{{#if isNullable}} + isNullable: {{{isNullable}}}, +{{/if}} +{{#if format}} + format: '{{{format}}}', +{{/if}} +{{#if maximum}} + maximum: {{{maximum}}}, +{{/if}} +{{#if exclusiveMaximum}} + exclusiveMaximum: {{{exclusiveMaximum}}}, +{{/if}} +{{#if minimum}} + minimum: {{{minimum}}}, +{{/if}} +{{#if exclusiveMinimum}} + exclusiveMinimum: {{{exclusiveMinimum}}}, +{{/if}} +{{#if multipleOf}} + multipleOf: {{{multipleOf}}}, +{{/if}} +{{#if maxLength}} + maxLength: {{{maxLength}}}, +{{/if}} +{{#if minLength}} + minLength: {{{minLength}}}, +{{/if}} +{{#if pattern}} + pattern: '{{{pattern}}}', +{{/if}} +{{#if maxItems}} + maxItems: {{{maxItems}}}, +{{/if}} +{{#if minItems}} + minItems: {{{minItems}}}, +{{/if}} +{{#if uniqueItems}} + uniqueItems: {{{uniqueItems}}}, +{{/if}} +{{#if maxProperties}} + maxProperties: {{{maxProperties}}}, +{{/if}} +{{#if minProperties}} + minProperties: {{{minProperties}}}, +{{/if}} +} diff --git a/packages/oas/openapi-typescript-codegen/src/templates/partials/schemaInterface.hbs b/packages/oas/openapi-typescript-codegen/src/templates/partials/schemaInterface.hbs new file mode 100644 index 0000000000..3417c5fb94 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/partials/schemaInterface.hbs @@ -0,0 +1,21 @@ +{ +{{#if description}} + description: `{{{escapeDescription description}}}`, +{{/if}} + properties: { +{{#if properties}} + {{#each properties}} + {{{name}}}: {{>schema}}, + {{/each}} +{{/if}} + }, +{{#if isReadOnly}} + isReadOnly: {{{isReadOnly}}}, +{{/if}} +{{#if isRequired}} + isRequired: {{{isRequired}}}, +{{/if}} +{{#if isNullable}} + isNullable: {{{isNullable}}}, +{{/if}} +} diff --git a/packages/oas/openapi-typescript-codegen/src/templates/partials/type.hbs b/packages/oas/openapi-typescript-codegen/src/templates/partials/type.hbs new file mode 100644 index 0000000000..b6b4834bd1 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/partials/type.hbs @@ -0,0 +1,19 @@ +{{#equals export 'interface'}} +{{>typeInterface}} +{{else equals export 'reference'}} +{{>typeReference}} +{{else equals export 'enum'}} +{{>typeEnum}} +{{else equals export 'array'}} +{{>typeArray}} +{{else equals export 'dictionary'}} +{{>typeDictionary}} +{{else equals export 'one-of'}} +{{>typeUnion}} +{{else equals export 'any-of'}} +{{>typeUnion}} +{{else equals export 'all-of'}} +{{>typeIntersection}} +{{else}} +{{>typeGeneric}} +{{/equals}} diff --git a/packages/oas/openapi-typescript-codegen/src/templates/partials/typeArray.hbs b/packages/oas/openapi-typescript-codegen/src/templates/partials/typeArray.hbs new file mode 100644 index 0000000000..c3d44374f4 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/partials/typeArray.hbs @@ -0,0 +1,5 @@ +{{~#if link~}} +Array<{{>type link}}>{{>isNullable}} +{{~else~}} +Array<{{>base}}>{{>isNullable}} +{{~/if~}} diff --git a/packages/oas/openapi-typescript-codegen/src/templates/partials/typeDictionary.hbs b/packages/oas/openapi-typescript-codegen/src/templates/partials/typeDictionary.hbs new file mode 100644 index 0000000000..12d57036c8 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/partials/typeDictionary.hbs @@ -0,0 +1,5 @@ +{{~#if link~}} +Recordtype link}}>{{>isNullable}} +{{~else~}} +Recordbase}}>{{>isNullable}} +{{~/if~}} diff --git a/packages/oas/openapi-typescript-codegen/src/templates/partials/typeEnum.hbs b/packages/oas/openapi-typescript-codegen/src/templates/partials/typeEnum.hbs new file mode 100644 index 0000000000..781fd5ec58 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/partials/typeEnum.hbs @@ -0,0 +1,2 @@ +{{#enumerator enum parent name}}{{this}}{{/enumerator}}{{>isNullable}} + diff --git a/packages/oas/openapi-typescript-codegen/src/templates/partials/typeGeneric.hbs b/packages/oas/openapi-typescript-codegen/src/templates/partials/typeGeneric.hbs new file mode 100644 index 0000000000..0ac6aea3b8 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/partials/typeGeneric.hbs @@ -0,0 +1 @@ +{{>base}}{{>isNullable}} diff --git a/packages/oas/openapi-typescript-codegen/src/templates/partials/typeInterface.hbs b/packages/oas/openapi-typescript-codegen/src/templates/partials/typeInterface.hbs new file mode 100644 index 0000000000..3d5acad072 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/partials/typeInterface.hbs @@ -0,0 +1,23 @@ +{{~#if properties~}} +{ +{{#each properties}} +{{#ifdef description deprecated}} +/** +{{#if description}} + * {{{escapeComment description}}} +{{/if}} +{{#if deprecated}} + * @deprecated +{{/if}} + */ +{{/ifdef}} +{{#if ../parent}} +{{>isReadOnly}}{{{name}}}{{>isRequired}}: {{>type parent=../parent}}; +{{else}} +{{>isReadOnly}}{{{name}}}{{>isRequired}}: {{>type}}; +{{/if}} +{{/each}} +}{{>isNullable}} +{{~else~}} +any +{{~/if~}} diff --git a/packages/oas/openapi-typescript-codegen/src/templates/partials/typeIntersection.hbs b/packages/oas/openapi-typescript-codegen/src/templates/partials/typeIntersection.hbs new file mode 100644 index 0000000000..031896f7c5 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/partials/typeIntersection.hbs @@ -0,0 +1 @@ +{{#intersection properties parent}}{{this}}{{/intersection}}{{>isNullable}} diff --git a/packages/oas/openapi-typescript-codegen/src/templates/partials/typeReference.hbs b/packages/oas/openapi-typescript-codegen/src/templates/partials/typeReference.hbs new file mode 100644 index 0000000000..0ac6aea3b8 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/partials/typeReference.hbs @@ -0,0 +1 @@ +{{>base}}{{>isNullable}} diff --git a/packages/oas/openapi-typescript-codegen/src/templates/partials/typeUnion.hbs b/packages/oas/openapi-typescript-codegen/src/templates/partials/typeUnion.hbs new file mode 100644 index 0000000000..59371dfe43 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/partials/typeUnion.hbs @@ -0,0 +1 @@ +{{#union properties parent}}{{this}}{{/union}}{{>isNullable}} diff --git a/packages/oas/openapi-typescript-codegen/src/templates/useClient.hbs b/packages/oas/openapi-typescript-codegen/src/templates/useClient.hbs new file mode 100644 index 0000000000..e99f26e731 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/templates/useClient.hbs @@ -0,0 +1,58 @@ +{{>header}} + +import React from 'react'; +import { createContext, useContext, ReactNode } from 'react'; +import { QueryClientProvider, QueryClientProviderProps } from 'react-query'; +{{#if packageNames.client}} +import { {{{clientName}}} } from '{{{packageNames.client}}}'; +{{else}} +import { {{{clientName}}} } from './{{{clientName}}}'; +{{/if}} + +interface {{{pascalCase clientName}}}ContextState { + client: {{{clientName}}} +} +const {{{pascalCase clientName}}}Context = createContext<{{{pascalCase clientName}}}ContextState | null>(null) + +export const use{{{pascalCase clientName}}} = () => { + const context = useContext({{{pascalCase clientName}}}Context) + if (!context) { + throw new Error("use{{{pascalCase clientName}}} must be used within a {{{pascalCase clientName}}}Provider") + } + return context +} + +interface {{{pascalCase clientName}}}ProviderProps { + baseUrl: string + queryClientProviderProps: QueryClientProviderProps + children: ReactNode + /** + * Authentication token + */ + apiKey?: string + /** + * PublishableApiKey identifier that defines the scope of resources + * available within the request + */ + publishableApiKey?: string +} + +export const {{{pascalCase clientName}}}Provider = ({ + queryClientProviderProps, + baseUrl, + {{!-- apiKey, + publishableApiKey,--}} + children, +}: {{{pascalCase clientName}}}ProviderProps) => { + const client = new {{{clientName}}}({ + BASE: baseUrl, + WITH_CREDENTIALS: true, + }) + return ( + + <{{{pascalCase clientName}}}Context.Provider value={ { client } }> + {children} + + + ) +} diff --git a/packages/oas/openapi-typescript-codegen/src/typings/hbs.d.ts b/packages/oas/openapi-typescript-codegen/src/typings/hbs.d.ts new file mode 100644 index 0000000000..baf6131250 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/typings/hbs.d.ts @@ -0,0 +1,16 @@ +/** + * We precompile the handlebar templates during the build process, + * however in the source code we want to reference these templates + * by importing the hbs files directly. Of course this is not allowed + * by Typescript, so we need to provide some declaration for these + * types. + * @see: build.js for more information + */ +declare module "*.hbs" { + const template: { + compiler: [number, string] + useData: true + main: () => void + } + export default template +} diff --git a/packages/oas/openapi-typescript-codegen/src/utils/discriminator.ts b/packages/oas/openapi-typescript-codegen/src/utils/discriminator.ts new file mode 100644 index 0000000000..56aff288f3 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/utils/discriminator.ts @@ -0,0 +1,53 @@ +import type { Model } from "../client/interfaces/Model" +import type { OpenApi } from "../openApi/v3/interfaces/OpenApi" +import type { OpenApiDiscriminator } from "../openApi/v3/interfaces/OpenApiDiscriminator" +import { stripNamespace } from "../openApi/v3/parser/stripNamespace" +import type { Dictionary } from "./types" + +const inverseDictionary = (map: Dictionary): Dictionary => { + const m2: Dictionary = {} + for (const key in map) { + m2[map[key]] = key + } + return m2 +} + +export const findOneOfParentDiscriminator = ( + openApi: OpenApi, + parent?: Model +): OpenApiDiscriminator | undefined => { + if (openApi.components && parent) { + for (const definitionName in openApi.components.schemas) { + if (openApi.components.schemas.hasOwnProperty(definitionName)) { + const schema = openApi.components.schemas[definitionName] + if ( + schema.discriminator && + schema.oneOf?.length && + schema.oneOf.some( + (definition) => + definition.$ref && stripNamespace(definition.$ref) == parent.name + ) + ) { + return schema.discriminator + } + } + } + } + return undefined +} + +export const mapPropertyValue = ( + discriminator: OpenApiDiscriminator, + parent: Model +): string => { + if (discriminator.mapping) { + const mapping = inverseDictionary(discriminator.mapping) + const key = Object.keys(mapping).find( + (item) => stripNamespace(item) == parent.name + ) + if (key && mapping[key]) { + return mapping[key] + } + } + return parent.name +} diff --git a/packages/oas/openapi-typescript-codegen/src/utils/fileSystem.ts b/packages/oas/openapi-typescript-codegen/src/utils/fileSystem.ts new file mode 100644 index 0000000000..5827442a6f --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/utils/fileSystem.ts @@ -0,0 +1,16 @@ +import { + copyFile as __copyFile, + mkdirp as __mkdirp, + pathExists as __pathExists, + readFile as __readFile, + remove as __remove, + writeFile as __writeFile, +} from "fs-extra" + +// Export calls (needed for mocking) +export const readFile = __readFile +export const writeFile = __writeFile +export const copyFile = __copyFile +export const exists = __pathExists +export const mkdir = __mkdirp +export const rmdir = __remove diff --git a/packages/oas/openapi-typescript-codegen/src/utils/flatMap.ts b/packages/oas/openapi-typescript-codegen/src/utils/flatMap.ts new file mode 100644 index 0000000000..b0af3be8d6 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/utils/flatMap.ts @@ -0,0 +1,14 @@ +/** + * Calls a defined callback on each element of an array. + * Then, flattens the result into a new array. + */ +export const flatMap = ( + array: T[], + callback: (value: T, index: number, array: T[]) => U[] +): U[] => { + const result: U[] = [] + array.map(callback).forEach((arr) => { + result.push(...arr) + }) + return result +} diff --git a/packages/oas/openapi-typescript-codegen/src/utils/formatCode.ts b/packages/oas/openapi-typescript-codegen/src/utils/formatCode.ts new file mode 100644 index 0000000000..26a682729b --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/utils/formatCode.ts @@ -0,0 +1,26 @@ +import { EOL } from "os" + +export const formatCode = (s: string): string => { + let indent: number = 0 + let lines = s.split(EOL) + lines = lines.map((line) => { + line = line.trim().replace(/^\*/g, " *") + let i = indent + if (line.endsWith("(") || line.endsWith("{") || line.endsWith("[")) { + indent++ + } + if ( + (line.startsWith(")") || line.startsWith("}") || line.startsWith("]")) && + i + ) { + indent-- + i-- + } + const result = `${"\t".repeat(i)}${line}` + if (result.trim() === "") { + return "" + } + return result + }) + return lines.join(EOL) +} diff --git a/packages/oas/openapi-typescript-codegen/src/utils/formatIndentation.ts b/packages/oas/openapi-typescript-codegen/src/utils/formatIndentation.ts new file mode 100644 index 0000000000..d0b3650974 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/utils/formatIndentation.ts @@ -0,0 +1,20 @@ +import { EOL } from "os" + +import { Indent } from "../Indent" + +export const formatIndentation = (s: string, indent: Indent): string => { + let lines = s.split(EOL) + lines = lines.map((line) => { + switch (indent) { + case Indent.SPACE_4: + return line.replace(/\t/g, " ") + case Indent.SPACE_2: + return line.replace(/\t/g, " ") + case Indent.TAB: + return line // Default output is tab formatted + } + }) + // Make sure we have a blank line at the end + const content = lines.join(EOL) + return `${content}${EOL}` +} diff --git a/packages/oas/openapi-typescript-codegen/src/utils/getHttpRequestName.ts b/packages/oas/openapi-typescript-codegen/src/utils/getHttpRequestName.ts new file mode 100644 index 0000000000..131516f399 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/utils/getHttpRequestName.ts @@ -0,0 +1,18 @@ +import { HttpClient } from "../HttpClient" + +/** + * Generate the HttpRequest filename based on the selected client + * @param httpClient The selected httpClient (fetch, xhr, node or axios) + */ +export const getHttpRequestName = (httpClient: HttpClient): string => { + switch (httpClient) { + case HttpClient.FETCH: + return "FetchHttpRequest" + case HttpClient.XHR: + return "XHRHttpRequest" + case HttpClient.NODE: + return "NodeHttpRequest" + case HttpClient.AXIOS: + return "AxiosHttpRequest" + } +} diff --git a/packages/oas/openapi-typescript-codegen/src/utils/getModelNames.ts b/packages/oas/openapi-typescript-codegen/src/utils/getModelNames.ts new file mode 100644 index 0000000000..3db3f84c28 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/utils/getModelNames.ts @@ -0,0 +1,6 @@ +import type { Model } from "../client/interfaces/Model" +import { sort } from "./sort" + +export const getModelNames = (models: Model[]): string[] => { + return models.map((model) => model.name).sort(sort) +} diff --git a/packages/oas/openapi-typescript-codegen/src/utils/getOpenApiSpec.ts b/packages/oas/openapi-typescript-codegen/src/utils/getOpenApiSpec.ts new file mode 100644 index 0000000000..db7d6a2dc4 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/utils/getOpenApiSpec.ts @@ -0,0 +1,11 @@ +import RefParser from "json-schema-ref-parser" + +/** + * Load and parse te open api spec. If the file extension is ".yml" or ".yaml" + * we will try to parse the file as a YAML spec, otherwise we will fall back + * on parsing the file as JSON. + * @param location: Path or url + */ +export const getOpenApiSpec = async (location: string): Promise => { + return await RefParser.bundle(location, location, {}) +} diff --git a/packages/oas/openapi-typescript-codegen/src/utils/getOpenApiVersion.ts b/packages/oas/openapi-typescript-codegen/src/utils/getOpenApiVersion.ts new file mode 100644 index 0000000000..cc0d7866d4 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/utils/getOpenApiVersion.ts @@ -0,0 +1,22 @@ +export enum OpenApiVersion { + V2 = 2, + V3 = 3, +} + +/** + * Get the Open API specification version (V2 or V3). This generator only supports + * version 2 and 3 of the specification, so we will alert the user if we encounter + * an incompatible type. Or if the type is missing... + * @param openApi The loaded spec (can be any object) + */ +export const getOpenApiVersion = (openApi: any): OpenApiVersion => { + const info: any = openApi.swagger || openApi.openapi + if (typeof info === "string") { + const c = info.charAt(0) + const v = Number.parseInt(c) + if (v === OpenApiVersion.V2 || v === OpenApiVersion.V3) { + return v as OpenApiVersion + } + } + throw new Error(`Unsupported Open API version: "${String(info)}"`) +} diff --git a/packages/oas/openapi-typescript-codegen/src/utils/getPattern.ts b/packages/oas/openapi-typescript-codegen/src/utils/getPattern.ts new file mode 100644 index 0000000000..3a6cf6ab6a --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/utils/getPattern.ts @@ -0,0 +1,14 @@ +/** + * The spec generates a pattern like this '^\d{3}-\d{2}-\d{4}$' + * However, to use it in HTML or inside new RegExp() we need to + * escape the pattern to become: '^\\d{3}-\\d{2}-\\d{4}$' in order + * to make it a valid regexp string. + * + * Also, escape single quote characters, because the output uses single quotes for strings + * + * @param pattern + */ +export const getPattern = (pattern?: string): string | undefined => { + // eslint-disable-next-line prettier/prettier + return pattern?.replace(/\\/g, "\\\\").replace(/'/g, "\\'") +} diff --git a/packages/oas/openapi-typescript-codegen/src/utils/getServiceNames.ts b/packages/oas/openapi-typescript-codegen/src/utils/getServiceNames.ts new file mode 100644 index 0000000000..e9a5e3b734 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/utils/getServiceNames.ts @@ -0,0 +1,6 @@ +import type { Service } from "../client/interfaces/Service" +import { sort } from "./sort" + +export const getServiceNames = (services: Service[]): string[] => { + return services.map((service) => service.name).sort(sort) +} diff --git a/packages/oas/openapi-typescript-codegen/src/utils/isDefined.ts b/packages/oas/openapi-typescript-codegen/src/utils/isDefined.ts new file mode 100644 index 0000000000..b450b60f7f --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/utils/isDefined.ts @@ -0,0 +1,9 @@ +/** + * Check if a value is defined + * @param value + */ +export const isDefined = ( + value: T | undefined | null | "" +): value is Exclude => { + return value !== undefined && value !== null && value !== "" +} diff --git a/packages/oas/openapi-typescript-codegen/src/utils/isEqual.ts b/packages/oas/openapi-typescript-codegen/src/utils/isEqual.ts new file mode 100644 index 0000000000..a18e2d36d8 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/utils/isEqual.ts @@ -0,0 +1,38 @@ +export const isEqual = (a: any, b: any): boolean => { + if (a === b) { + return true + } + + if (a && b && typeof a === "object" && typeof b === "object") { + if (Array.isArray(a) && Array.isArray(b)) { + if (a.length !== b.length) { + return false + } + for (let i = 0; i < a.length; i++) { + if (!isEqual(a[i], b[i])) { + return false + } + } + return true + } + + const keysA = Object.keys(a) + const keysB = Object.keys(b) + if (keysA.length !== keysB.length) { + return false + } + + for (let i = 0; i < keysA.length; i++) { + const key = keysA[i] + if (!Object.prototype.hasOwnProperty.call(b, key)) { + return false + } + if (!isEqual(a[key], b[key])) { + return false + } + } + return true + } + + return a !== a && b !== b +} diff --git a/packages/oas/openapi-typescript-codegen/src/utils/isString.ts b/packages/oas/openapi-typescript-codegen/src/utils/isString.ts new file mode 100644 index 0000000000..6e38486b54 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/utils/isString.ts @@ -0,0 +1,3 @@ +export const isString = (val: any): val is string => { + return typeof val === "string" +} diff --git a/packages/oas/openapi-typescript-codegen/src/utils/isSubdirectory.ts b/packages/oas/openapi-typescript-codegen/src/utils/isSubdirectory.ts new file mode 100644 index 0000000000..96aa666d27 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/utils/isSubdirectory.ts @@ -0,0 +1,5 @@ +import { relative } from "path" + +export const isSubDirectory = (parent: string, child: string) => { + return relative(child, parent).startsWith("..") +} diff --git a/packages/oas/openapi-typescript-codegen/src/utils/postProcessClient.ts b/packages/oas/openapi-typescript-codegen/src/utils/postProcessClient.ts new file mode 100644 index 0000000000..2bb5b005e3 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/utils/postProcessClient.ts @@ -0,0 +1,15 @@ +import type { Client } from "../client/interfaces/Client" +import { postProcessModel } from "./postProcessModel" +import { postProcessService } from "./postProcessService" + +/** + * Post process client + * @param client Client object with all the models, services, etc. + */ +export const postProcessClient = (client: Client): Client => { + return { + ...client, + models: client.models.map((model) => postProcessModel(model)), + services: client.services.map((service) => postProcessService(service)), + } +} diff --git a/packages/oas/openapi-typescript-codegen/src/utils/postProcessModel.ts b/packages/oas/openapi-typescript-codegen/src/utils/postProcessModel.ts new file mode 100644 index 0000000000..2558c92c38 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/utils/postProcessModel.ts @@ -0,0 +1,18 @@ +import type { Model } from "../client/interfaces/Model" +import { postProcessModelEnum } from "./postProcessModelEnum" +import { postProcessModelEnums } from "./postProcessModelEnums" +import { postProcessModelImports } from "./postProcessModelImports" + +/** + * Post processes the model. + * This will clean up any double imports or enum values. + * @param model + */ +export const postProcessModel = (model: Model): Model => { + return { + ...model, + imports: postProcessModelImports(model), + enums: postProcessModelEnums(model), + enum: postProcessModelEnum(model), + } +} diff --git a/packages/oas/openapi-typescript-codegen/src/utils/postProcessModelEnum.ts b/packages/oas/openapi-typescript-codegen/src/utils/postProcessModelEnum.ts new file mode 100644 index 0000000000..a4352ea577 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/utils/postProcessModelEnum.ts @@ -0,0 +1,12 @@ +import type { Enum } from "../client/interfaces/Enum" +import type { Model } from "../client/interfaces/Model" + +/** + * Set unique enum values for the model + * @param model + */ +export const postProcessModelEnum = (model: Model): Enum[] => { + return model.enum.filter((property, index, arr) => { + return arr.findIndex((item) => item.name === property.name) === index + }) +} diff --git a/packages/oas/openapi-typescript-codegen/src/utils/postProcessModelEnums.ts b/packages/oas/openapi-typescript-codegen/src/utils/postProcessModelEnums.ts new file mode 100644 index 0000000000..53df1a100b --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/utils/postProcessModelEnums.ts @@ -0,0 +1,11 @@ +import type { Model } from "../client/interfaces/Model" + +/** + * Set unique enum values for the model + * @param model The model that is post-processed + */ +export const postProcessModelEnums = (model: Model): Model[] => { + return model.enums.filter((property, index, arr) => { + return arr.findIndex((item) => item.name === property.name) === index + }) +} diff --git a/packages/oas/openapi-typescript-codegen/src/utils/postProcessModelImports.ts b/packages/oas/openapi-typescript-codegen/src/utils/postProcessModelImports.ts new file mode 100644 index 0000000000..753ffd6f2d --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/utils/postProcessModelImports.ts @@ -0,0 +1,14 @@ +import type { Model } from "../client/interfaces/Model" +import { sort } from "./sort" +import { unique } from "./unique" + +/** + * Set unique imports, sorted by name + * @param model The model that is post-processed + */ +export const postProcessModelImports = (model: Model): string[] => { + return model.imports + .filter(unique) + .sort(sort) + .filter((name) => model.name !== name) +} diff --git a/packages/oas/openapi-typescript-codegen/src/utils/postProcessService.ts b/packages/oas/openapi-typescript-codegen/src/utils/postProcessService.ts new file mode 100644 index 0000000000..c6487feef7 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/utils/postProcessService.ts @@ -0,0 +1,13 @@ +import type { Service } from "../client/interfaces/Service" +import { postProcessServiceImports } from "./postProcessServiceImports" +import { postProcessServiceOperations } from "./postProcessServiceOperations" + +export const postProcessService = (service: Service): Service => { + const clone = { ...service } + clone.operations = postProcessServiceOperations(clone) + clone.operations.forEach((operation) => { + clone.imports.push(...operation.imports) + }) + clone.imports = postProcessServiceImports(clone) + return clone +} diff --git a/packages/oas/openapi-typescript-codegen/src/utils/postProcessServiceImports.ts b/packages/oas/openapi-typescript-codegen/src/utils/postProcessServiceImports.ts new file mode 100644 index 0000000000..f1a6609169 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/utils/postProcessServiceImports.ts @@ -0,0 +1,11 @@ +import type { Service } from "../client/interfaces/Service" +import { sort } from "./sort" +import { unique } from "./unique" + +/** + * Set unique imports, sorted by name + * @param service + */ +export const postProcessServiceImports = (service: Service): string[] => { + return service.imports.filter(unique).sort(sort) +} diff --git a/packages/oas/openapi-typescript-codegen/src/utils/postProcessServiceOperations.ts b/packages/oas/openapi-typescript-codegen/src/utils/postProcessServiceOperations.ts new file mode 100644 index 0000000000..0288db9414 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/utils/postProcessServiceOperations.ts @@ -0,0 +1,28 @@ +import type { Operation } from "../client/interfaces/Operation" +import type { Service } from "../client/interfaces/Service" +import { flatMap } from "./flatMap" + +export const postProcessServiceOperations = (service: Service): Operation[] => { + const names = new Map() + + return service.operations.map((operation) => { + const clone = { ...operation } + + // Parse the service parameters and results, very similar to how we parse + // properties of models. These methods will extend the type if needed. + clone.imports.push( + ...flatMap(clone.parameters, (parameter) => parameter.imports) + ) + clone.imports.push(...flatMap(clone.results, (result) => result.imports)) + + // Check if the operation name is unique, if not then prefix this with a number + const name = clone.name + const index = names.get(name) || 0 + if (index > 0) { + clone.name = `${name}${index}` + } + names.set(name, index + 1) + + return clone + }) +} diff --git a/packages/oas/openapi-typescript-codegen/src/utils/readSpec.ts b/packages/oas/openapi-typescript-codegen/src/utils/readSpec.ts new file mode 100644 index 0000000000..87c1f40122 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/utils/readSpec.ts @@ -0,0 +1,13 @@ +import { readSpecFromDisk } from "./readSpecFromDisk" +import { readSpecFromHttp } from "./readSpecFromHttp" +import { readSpecFromHttps } from "./readSpecFromHttps" + +export const readSpec = async (input: string): Promise => { + if (input.startsWith("https://")) { + return await readSpecFromHttps(input) + } + if (input.startsWith("http://")) { + return await readSpecFromHttp(input) + } + return await readSpecFromDisk(input) +} diff --git a/packages/oas/openapi-typescript-codegen/src/utils/readSpecFromDisk.ts b/packages/oas/openapi-typescript-codegen/src/utils/readSpecFromDisk.ts new file mode 100644 index 0000000000..03759d7c7f --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/utils/readSpecFromDisk.ts @@ -0,0 +1,21 @@ +import { resolve } from "path" + +import { exists, readFile } from "./fileSystem" + +/** + * Check if given file exists and try to read the content as string. + * @param input + */ +export const readSpecFromDisk = async (input: string): Promise => { + const filePath = resolve(process.cwd(), input) + const fileExists = await exists(filePath) + if (fileExists) { + try { + const content = await readFile(filePath, "utf8") + return content.toString() + } catch (e) { + throw new Error(`Could not read OpenApi spec: "${filePath}"`) + } + } + throw new Error(`Could not find OpenApi spec: "${filePath}"`) +} diff --git a/packages/oas/openapi-typescript-codegen/src/utils/readSpecFromHttp.ts b/packages/oas/openapi-typescript-codegen/src/utils/readSpecFromHttp.ts new file mode 100644 index 0000000000..35dc5c9637 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/utils/readSpecFromHttp.ts @@ -0,0 +1,22 @@ +import { get } from "http" + +/** + * Download the spec file from a HTTP resource + * @param url + */ +export const readSpecFromHttp = async (url: string): Promise => { + return new Promise((resolve, reject) => { + get(url, (response) => { + let body = "" + response.on("data", (chunk) => { + body += chunk + }) + response.on("end", () => { + resolve(body) + }) + response.on("error", () => { + reject(`Could not read OpenApi spec: "${url}"`) + }) + }) + }) +} diff --git a/packages/oas/openapi-typescript-codegen/src/utils/readSpecFromHttps.ts b/packages/oas/openapi-typescript-codegen/src/utils/readSpecFromHttps.ts new file mode 100644 index 0000000000..54bbb25eff --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/utils/readSpecFromHttps.ts @@ -0,0 +1,22 @@ +import { get } from "https" + +/** + * Download the spec file from a HTTPS resource + * @param url + */ +export const readSpecFromHttps = async (url: string): Promise => { + return new Promise((resolve, reject) => { + get(url, (response) => { + let body = "" + response.on("data", (chunk) => { + body += chunk + }) + response.on("end", () => { + resolve(body) + }) + response.on("error", () => { + reject(`Could not read OpenApi spec: "${url}"`) + }) + }) + }) +} diff --git a/packages/oas/openapi-typescript-codegen/src/utils/registerHandlebarHelpers.ts b/packages/oas/openapi-typescript-codegen/src/utils/registerHandlebarHelpers.ts new file mode 100644 index 0000000000..e8c7b79ee4 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/utils/registerHandlebarHelpers.ts @@ -0,0 +1,146 @@ +import camelCase from "camelcase" +import pascalCase from "pascalcase" +import Handlebars from "handlebars/runtime" +import { EOL } from "os" + +import type { Enum } from "../client/interfaces/Enum" +import type { Model } from "../client/interfaces/Model" +import type { HttpClient } from "../HttpClient" +import { unique } from "./unique" + +export const registerHandlebarHelpers = (root: { + httpClient: HttpClient + useOptions: boolean + useUnionTypes: boolean +}): void => { + Handlebars.registerHelper("ifdef", function (this: any, ...args): string { + const options = args.pop() + if (!args.every((value) => !value)) { + return options.fn(this) + } + return options.inverse(this) + }) + + Handlebars.registerHelper( + "equals", + function ( + this: any, + a: string, + b: string, + options: Handlebars.HelperOptions + ): string { + return a === b ? options.fn(this) : options.inverse(this) + } + ) + + Handlebars.registerHelper( + "notEquals", + function ( + this: any, + a: string, + b: string, + options: Handlebars.HelperOptions + ): string { + return a !== b ? options.fn(this) : options.inverse(this) + } + ) + + Handlebars.registerHelper( + "containsSpaces", + function ( + this: any, + value: string, + options: Handlebars.HelperOptions + ): string { + return /\s+/.test(value) ? options.fn(this) : options.inverse(this) + } + ) + + Handlebars.registerHelper( + "union", + function ( + this: any, + properties: Model[], + parent: string | undefined, + options: Handlebars.HelperOptions + ) { + const type = Handlebars.partials["type"] + const types = properties.map((property) => + type({ ...root, ...property, parent }) + ) + const uniqueTypes = types.filter(unique) + let uniqueTypesString = uniqueTypes.join(" | ") + if (uniqueTypes.length > 1) { + uniqueTypesString = `(${uniqueTypesString})` + } + return options.fn(uniqueTypesString) + } + ) + + Handlebars.registerHelper( + "intersection", + function ( + this: any, + properties: Model[], + parent: string | undefined, + options: Handlebars.HelperOptions + ) { + const type = Handlebars.partials["type"] + const types = properties.map((property) => + type({ ...root, ...property, parent }) + ) + const uniqueTypes = types.filter(unique) + let uniqueTypesString = uniqueTypes.join(" & ") + if (uniqueTypes.length > 1) { + uniqueTypesString = `(${uniqueTypesString})` + } + return options.fn(uniqueTypesString) + } + ) + + Handlebars.registerHelper( + "enumerator", + function ( + this: any, + enumerators: Enum[], + parent: string | undefined, + name: string | undefined, + options: Handlebars.HelperOptions + ) { + if (!root.useUnionTypes && parent && name) { + return `${parent}.${name}` + } + return options.fn( + enumerators + .map((enumerator) => enumerator.value) + .filter(unique) + .join(" | ") + ) + } + ) + + Handlebars.registerHelper("escapeComment", function (value: string): string { + return value + .replace(/\*\//g, "*") + .replace(/\/\*/g, "*") + .replace(/\r?\n(.*)/g, (_, w) => `${EOL} * ${w.trim()}`) + }) + + Handlebars.registerHelper( + "escapeDescription", + function (value: string): string { + return value + .replace(/\\/g, "\\\\") + .replace(/`/g, "\\`") + .replace(/\${/g, "\\${") + } + ) + + Handlebars.registerHelper("camelCase", function (value: string): string { + return camelCase(value) + }) + + Handlebars.registerHelper("pascalCase", function (value: string): string { + return pascalCase(value) + }) +} diff --git a/packages/oas/openapi-typescript-codegen/src/utils/registerHandlebarTemplates.ts b/packages/oas/openapi-typescript-codegen/src/utils/registerHandlebarTemplates.ts new file mode 100644 index 0000000000..ce9464ad5b --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/utils/registerHandlebarTemplates.ts @@ -0,0 +1,390 @@ +import Handlebars from "handlebars/runtime" + +import { HttpClient } from "../HttpClient" +import templateClient from "../templates/client.hbs" +import templateUseClient from "../templates/useClient.hbs" +import templateCoreApiError from "../templates/core/ApiError.hbs" +import templateCoreApiRequestOptions from "../templates/core/ApiRequestOptions.hbs" +import templateCoreApiResult from "../templates/core/ApiResult.hbs" +import templateCoreHookUtils from "../templates/core/HookUtils.hbs" +import axiosGetHeaders from "../templates/core/axios/getHeaders.hbs" +import axiosGetRequestBody from "../templates/core/axios/getRequestBody.hbs" +import axiosGetResponseBody from "../templates/core/axios/getResponseBody.hbs" +import axiosGetResponseHeader from "../templates/core/axios/getResponseHeader.hbs" +import axiosRequest from "../templates/core/axios/request.hbs" +import axiosSendRequest from "../templates/core/axios/sendRequest.hbs" +import templateCoreBaseHttpRequest from "../templates/core/BaseHttpRequest.hbs" +import templateCancelablePromise from "../templates/core/CancelablePromise.hbs" +import fetchGetHeaders from "../templates/core/fetch/getHeaders.hbs" +import fetchGetRequestBody from "../templates/core/fetch/getRequestBody.hbs" +import fetchGetResponseBody from "../templates/core/fetch/getResponseBody.hbs" +import fetchGetResponseHeader from "../templates/core/fetch/getResponseHeader.hbs" +import fetchRequest from "../templates/core/fetch/request.hbs" +import fetchSendRequest from "../templates/core/fetch/sendRequest.hbs" +import functionBase64 from "../templates/core/functions/base64.hbs" +import functionCatchErrorCodes from "../templates/core/functions/catchErrorCodes.hbs" +import functionGetFormData from "../templates/core/functions/getFormData.hbs" +import functionGetQueryString from "../templates/core/functions/getQueryString.hbs" +import functionGetUrl from "../templates/core/functions/getUrl.hbs" +import functionIsBlob from "../templates/core/functions/isBlob.hbs" +import functionIsDefined from "../templates/core/functions/isDefined.hbs" +import functionIsFormData from "../templates/core/functions/isFormData.hbs" +import functionIsString from "../templates/core/functions/isString.hbs" +import functionIsStringWithValue from "../templates/core/functions/isStringWithValue.hbs" +import functionIsSuccess from "../templates/core/functions/isSuccess.hbs" +import functionResolve from "../templates/core/functions/resolve.hbs" +import templateCoreHttpRequest from "../templates/core/HttpRequest.hbs" +import nodeGetHeaders from "../templates/core/node/getHeaders.hbs" +import nodeGetRequestBody from "../templates/core/node/getRequestBody.hbs" +import nodeGetResponseBody from "../templates/core/node/getResponseBody.hbs" +import nodeGetResponseHeader from "../templates/core/node/getResponseHeader.hbs" +import nodeRequest from "../templates/core/node/request.hbs" +import nodeSendRequest from "../templates/core/node/sendRequest.hbs" +import templateCoreSettings from "../templates/core/OpenAPI.hbs" +import templateCoreRequest from "../templates/core/request.hbs" +import xhrGetHeaders from "../templates/core/xhr/getHeaders.hbs" +import xhrGetRequestBody from "../templates/core/xhr/getRequestBody.hbs" +import xhrGetResponseBody from "../templates/core/xhr/getResponseBody.hbs" +import xhrGetResponseHeader from "../templates/core/xhr/getResponseHeader.hbs" +import xhrRequest from "../templates/core/xhr/request.hbs" +import xhrSendRequest from "../templates/core/xhr/sendRequest.hbs" +import templateExportModel from "../templates/exportModel.hbs" +import templateExportSchema from "../templates/exportSchema.hbs" +import templateExportService from "../templates/exportService.hbs" +import templateExportHook from "../templates/exportHook.hbs" +import templateIndex from "../templates/indexes/index.hbs" +import templateIndexModels from "../templates/indexes/indexModels.hbs" +import templateIndexServices from "../templates/indexes/indexServices.hbs" +import templateIndexHooks from "../templates/indexes/indexHooks.hbs" +import partialBase from "../templates/partials/base.hbs" +import partialExportComposition from "../templates/partials/exportComposition.hbs" +import partialExportEnum from "../templates/partials/exportEnum.hbs" +import partialExportInterface from "../templates/partials/exportInterface.hbs" +import partialExportType from "../templates/partials/exportType.hbs" +import partialHeader from "../templates/partials/header.hbs" +import partialIsNullable from "../templates/partials/isNullable.hbs" +import partialIsReadOnly from "../templates/partials/isReadOnly.hbs" +import partialIsRequired from "../templates/partials/isRequired.hbs" +import partialParameters from "../templates/partials/parameters.hbs" +import partialParametersUntyped from "../templates/partials/parametersUntyped.hbs" +import partialResult from "../templates/partials/result.hbs" +import partialSchema from "../templates/partials/schema.hbs" +import partialSchemaArray from "../templates/partials/schemaArray.hbs" +import partialSchemaComposition from "../templates/partials/schemaComposition.hbs" +import partialSchemaDictionary from "../templates/partials/schemaDictionary.hbs" +import partialSchemaEnum from "../templates/partials/schemaEnum.hbs" +import partialSchemaGeneric from "../templates/partials/schemaGeneric.hbs" +import partialSchemaInterface from "../templates/partials/schemaInterface.hbs" +import partialType from "../templates/partials/type.hbs" +import partialTypeArray from "../templates/partials/typeArray.hbs" +import partialTypeDictionary from "../templates/partials/typeDictionary.hbs" +import partialTypeEnum from "../templates/partials/typeEnum.hbs" +import partialTypeGeneric from "../templates/partials/typeGeneric.hbs" +import partialTypeInterface from "../templates/partials/typeInterface.hbs" +import partialTypeIntersection from "../templates/partials/typeIntersection.hbs" +import partialTypeReference from "../templates/partials/typeReference.hbs" +import partialTypeUnion from "../templates/partials/typeUnion.hbs" +import { registerHandlebarHelpers } from "./registerHandlebarHelpers" + +export interface Templates { + indexes: { + index: Handlebars.TemplateDelegate + indexModels: Handlebars.TemplateDelegate + indexServices: Handlebars.TemplateDelegate + indexHooks: Handlebars.TemplateDelegate + } + client: Handlebars.TemplateDelegate + useClient: Handlebars.TemplateDelegate + exports: { + model: Handlebars.TemplateDelegate + schema: Handlebars.TemplateDelegate + service: Handlebars.TemplateDelegate + hook: Handlebars.TemplateDelegate + } + core: { + settings: Handlebars.TemplateDelegate + apiError: Handlebars.TemplateDelegate + apiRequestOptions: Handlebars.TemplateDelegate + apiResult: Handlebars.TemplateDelegate + cancelablePromise: Handlebars.TemplateDelegate + request: Handlebars.TemplateDelegate + baseHttpRequest: Handlebars.TemplateDelegate + httpRequest: Handlebars.TemplateDelegate + hookUtils: Handlebars.TemplateDelegate + } +} + +/** + * Read all the Handlebar templates that we need and return on wrapper object + * so we can easily access the templates in out generator / write functions. + */ +export const registerHandlebarTemplates = (root: { + httpClient: HttpClient + useOptions: boolean + useUnionTypes: boolean +}): Templates => { + registerHandlebarHelpers(root) + + // Main templates (entry points for the files we write to disk) + const templates: Templates = { + indexes: { + index: Handlebars.template(templateIndex), + indexModels: Handlebars.template(templateIndexModels), + indexServices: Handlebars.template(templateIndexServices), + indexHooks: Handlebars.template(templateIndexHooks), + }, + client: Handlebars.template(templateClient), + useClient: Handlebars.template(templateUseClient), + exports: { + model: Handlebars.template(templateExportModel), + schema: Handlebars.template(templateExportSchema), + service: Handlebars.template(templateExportService), + hook: Handlebars.template(templateExportHook), + }, + core: { + settings: Handlebars.template(templateCoreSettings), + apiError: Handlebars.template(templateCoreApiError), + apiRequestOptions: Handlebars.template(templateCoreApiRequestOptions), + apiResult: Handlebars.template(templateCoreApiResult), + cancelablePromise: Handlebars.template(templateCancelablePromise), + request: Handlebars.template(templateCoreRequest), + baseHttpRequest: Handlebars.template(templateCoreBaseHttpRequest), + httpRequest: Handlebars.template(templateCoreHttpRequest), + hookUtils: Handlebars.template(templateCoreHookUtils), + }, + } + + // Partials for the generations of the models, services, etc. + Handlebars.registerPartial( + "exportEnum", + Handlebars.template(partialExportEnum) + ) + Handlebars.registerPartial( + "exportInterface", + Handlebars.template(partialExportInterface) + ) + Handlebars.registerPartial( + "exportComposition", + Handlebars.template(partialExportComposition) + ) + Handlebars.registerPartial( + "exportType", + Handlebars.template(partialExportType) + ) + Handlebars.registerPartial("header", Handlebars.template(partialHeader)) + Handlebars.registerPartial( + "isNullable", + Handlebars.template(partialIsNullable) + ) + Handlebars.registerPartial( + "isReadOnly", + Handlebars.template(partialIsReadOnly) + ) + Handlebars.registerPartial( + "isRequired", + Handlebars.template(partialIsRequired) + ) + Handlebars.registerPartial( + "parameters", + Handlebars.template(partialParameters) + ) + Handlebars.registerPartial( + "parametersUntyped", + Handlebars.template(partialParametersUntyped) + ) + Handlebars.registerPartial("result", Handlebars.template(partialResult)) + Handlebars.registerPartial("schema", Handlebars.template(partialSchema)) + Handlebars.registerPartial( + "schemaArray", + Handlebars.template(partialSchemaArray) + ) + Handlebars.registerPartial( + "schemaDictionary", + Handlebars.template(partialSchemaDictionary) + ) + Handlebars.registerPartial( + "schemaEnum", + Handlebars.template(partialSchemaEnum) + ) + Handlebars.registerPartial( + "schemaGeneric", + Handlebars.template(partialSchemaGeneric) + ) + Handlebars.registerPartial( + "schemaInterface", + Handlebars.template(partialSchemaInterface) + ) + Handlebars.registerPartial( + "schemaComposition", + Handlebars.template(partialSchemaComposition) + ) + Handlebars.registerPartial("type", Handlebars.template(partialType)) + Handlebars.registerPartial("typeArray", Handlebars.template(partialTypeArray)) + Handlebars.registerPartial( + "typeDictionary", + Handlebars.template(partialTypeDictionary) + ) + Handlebars.registerPartial("typeEnum", Handlebars.template(partialTypeEnum)) + Handlebars.registerPartial( + "typeGeneric", + Handlebars.template(partialTypeGeneric) + ) + Handlebars.registerPartial( + "typeInterface", + Handlebars.template(partialTypeInterface) + ) + Handlebars.registerPartial( + "typeReference", + Handlebars.template(partialTypeReference) + ) + Handlebars.registerPartial("typeUnion", Handlebars.template(partialTypeUnion)) + Handlebars.registerPartial( + "typeIntersection", + Handlebars.template(partialTypeIntersection) + ) + Handlebars.registerPartial("base", Handlebars.template(partialBase)) + + // Generic functions used in 'request' file @see src/templates/core/request.hbs for more info + Handlebars.registerPartial( + "functions/catchErrorCodes", + Handlebars.template(functionCatchErrorCodes) + ) + Handlebars.registerPartial( + "functions/getFormData", + Handlebars.template(functionGetFormData) + ) + Handlebars.registerPartial( + "functions/getQueryString", + Handlebars.template(functionGetQueryString) + ) + Handlebars.registerPartial( + "functions/getUrl", + Handlebars.template(functionGetUrl) + ) + Handlebars.registerPartial( + "functions/isBlob", + Handlebars.template(functionIsBlob) + ) + Handlebars.registerPartial( + "functions/isDefined", + Handlebars.template(functionIsDefined) + ) + Handlebars.registerPartial( + "functions/isFormData", + Handlebars.template(functionIsFormData) + ) + Handlebars.registerPartial( + "functions/isString", + Handlebars.template(functionIsString) + ) + Handlebars.registerPartial( + "functions/isStringWithValue", + Handlebars.template(functionIsStringWithValue) + ) + Handlebars.registerPartial( + "functions/isSuccess", + Handlebars.template(functionIsSuccess) + ) + Handlebars.registerPartial( + "functions/base64", + Handlebars.template(functionBase64) + ) + Handlebars.registerPartial( + "functions/resolve", + Handlebars.template(functionResolve) + ) + + // Specific files for the fetch client implementation + Handlebars.registerPartial( + "fetch/getHeaders", + Handlebars.template(fetchGetHeaders) + ) + Handlebars.registerPartial( + "fetch/getRequestBody", + Handlebars.template(fetchGetRequestBody) + ) + Handlebars.registerPartial( + "fetch/getResponseBody", + Handlebars.template(fetchGetResponseBody) + ) + Handlebars.registerPartial( + "fetch/getResponseHeader", + Handlebars.template(fetchGetResponseHeader) + ) + Handlebars.registerPartial( + "fetch/sendRequest", + Handlebars.template(fetchSendRequest) + ) + Handlebars.registerPartial("fetch/request", Handlebars.template(fetchRequest)) + + // Specific files for the xhr client implementation + Handlebars.registerPartial( + "xhr/getHeaders", + Handlebars.template(xhrGetHeaders) + ) + Handlebars.registerPartial( + "xhr/getRequestBody", + Handlebars.template(xhrGetRequestBody) + ) + Handlebars.registerPartial( + "xhr/getResponseBody", + Handlebars.template(xhrGetResponseBody) + ) + Handlebars.registerPartial( + "xhr/getResponseHeader", + Handlebars.template(xhrGetResponseHeader) + ) + Handlebars.registerPartial( + "xhr/sendRequest", + Handlebars.template(xhrSendRequest) + ) + Handlebars.registerPartial("xhr/request", Handlebars.template(xhrRequest)) + + // Specific files for the node client implementation + Handlebars.registerPartial( + "node/getHeaders", + Handlebars.template(nodeGetHeaders) + ) + Handlebars.registerPartial( + "node/getRequestBody", + Handlebars.template(nodeGetRequestBody) + ) + Handlebars.registerPartial( + "node/getResponseBody", + Handlebars.template(nodeGetResponseBody) + ) + Handlebars.registerPartial( + "node/getResponseHeader", + Handlebars.template(nodeGetResponseHeader) + ) + Handlebars.registerPartial( + "node/sendRequest", + Handlebars.template(nodeSendRequest) + ) + Handlebars.registerPartial("node/request", Handlebars.template(nodeRequest)) + + // Specific files for the axios client implementation + Handlebars.registerPartial( + "axios/getHeaders", + Handlebars.template(axiosGetHeaders) + ) + Handlebars.registerPartial( + "axios/getRequestBody", + Handlebars.template(axiosGetRequestBody) + ) + Handlebars.registerPartial( + "axios/getResponseBody", + Handlebars.template(axiosGetResponseBody) + ) + Handlebars.registerPartial( + "axios/getResponseHeader", + Handlebars.template(axiosGetResponseHeader) + ) + Handlebars.registerPartial( + "axios/sendRequest", + Handlebars.template(axiosSendRequest) + ) + Handlebars.registerPartial("axios/request", Handlebars.template(axiosRequest)) + + return templates +} diff --git a/packages/oas/openapi-typescript-codegen/src/utils/sort.ts b/packages/oas/openapi-typescript-codegen/src/utils/sort.ts new file mode 100644 index 0000000000..18d2ecaddd --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/utils/sort.ts @@ -0,0 +1,5 @@ +export const sort = (a: string, b: string): number => { + const nameA = a.toLowerCase() + const nameB = b.toLowerCase() + return nameA.localeCompare(nameB, "en") +} diff --git a/packages/oas/openapi-typescript-codegen/src/utils/sortModelsByName.ts b/packages/oas/openapi-typescript-codegen/src/utils/sortModelsByName.ts new file mode 100644 index 0000000000..9d3e915b72 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/utils/sortModelsByName.ts @@ -0,0 +1,9 @@ +import type { Model } from "../client/interfaces/Model" + +export const sortModelsByName = (models: Model[]): Model[] => { + return models.sort((a, b) => { + const nameA = a.name.toLowerCase() + const nameB = b.name.toLowerCase() + return nameA.localeCompare(nameB, "en") + }) +} diff --git a/packages/oas/openapi-typescript-codegen/src/utils/sortServicesByName.ts b/packages/oas/openapi-typescript-codegen/src/utils/sortServicesByName.ts new file mode 100644 index 0000000000..14a9d0faf8 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/utils/sortServicesByName.ts @@ -0,0 +1,9 @@ +import type { Service } from "../client/interfaces/Service" + +export const sortServicesByName = (services: Service[]): Service[] => { + return services.sort((a, b) => { + const nameA = a.name.toLowerCase() + const nameB = b.name.toLowerCase() + return nameA.localeCompare(nameB, "en") + }) +} diff --git a/packages/oas/openapi-typescript-codegen/src/utils/types.d.ts b/packages/oas/openapi-typescript-codegen/src/utils/types.d.ts new file mode 100644 index 0000000000..a3c299913b --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/utils/types.d.ts @@ -0,0 +1,3 @@ +export interface Dictionary { + [key: string]: T +} diff --git a/packages/oas/openapi-typescript-codegen/src/utils/unique.ts b/packages/oas/openapi-typescript-codegen/src/utils/unique.ts new file mode 100644 index 0000000000..9705cdb55d --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/utils/unique.ts @@ -0,0 +1,3 @@ +export const unique = (val: T, index: number, arr: T[]): boolean => { + return arr.indexOf(val) === index +} diff --git a/packages/oas/openapi-typescript-codegen/src/utils/writeClient.ts b/packages/oas/openapi-typescript-codegen/src/utils/writeClient.ts new file mode 100644 index 0000000000..de610f261a --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/utils/writeClient.ts @@ -0,0 +1,247 @@ +import { resolve } from "path" + +import type { Client } from "../client/interfaces/Client" +import type { HttpClient } from "../HttpClient" +import type { Indent } from "../Indent" +import { mkdir, rmdir, writeFile } from "./fileSystem" +import { isDefined } from "./isDefined" +import { isSubDirectory } from "./isSubdirectory" +import type { Templates } from "./registerHandlebarTemplates" +import { writeClientClass } from "./writeClientClass" +import { writeClientCore } from "./writeClientCore" +import { writeClientHooks } from "./writeClientHooks" +import { + writeClientIndex, + writeClientIndexHooks, + writeClientIndexModels, + writeClientIndexServices, +} from "./writeClientIndex" +import { writeClientModels } from "./writeClientModels" +import { writeClientSchemas } from "./writeClientSchemas" +import { writeClientServices } from "./writeClientServices" +import { writeUseClient } from "./writeUseClient" +import { PackageNames } from "../index" +import { formatIndentation as i } from "./formatIndentation" + +/** + * Write our OpenAPI client, using the given templates at the given output + * @param client Client object with all the models, services, etc. + * @param templates Templates wrapper with all loaded Handlebars templates + * @param output The relative location of the output directory + * @param httpClient The selected httpClient (fetch, xhr, node or axios) + * @param useOptions Use options or arguments functions + * @param useUnionTypes Use union types instead of enums + * @param exportCore Generate core client classes + * @param exportServices Generate services + * @param exportModels Generate models + * @param exportHooks Generate hooks + * @param exportSchemas Generate schemas + * @param exportSchemas Generate schemas + * @param indent Indentation options (4, 2 or tab) + * @param packageNames Package name to use in import statements. + * @param postfixServices Service name postfix + * @param postfixModels Model name postfix + * @param clientName Custom client class name + * @param request Path to custom request file + */ +export const writeClient = async ( + client: Client, + templates: Templates, + output: string, + httpClient: HttpClient, + useOptions: boolean, + useUnionTypes: boolean, + exportCore: boolean, + exportServices: boolean, + exportModels: boolean, + exportHooks: boolean, + exportSchemas: boolean, + indent: Indent, + packageNames: PackageNames, + postfixServices: string, + postfixModels: string, + clientName?: string, + request?: string +): Promise => { + const outputPath = resolve(process.cwd(), output) + const outputPathCore = resolve(outputPath, "core") + const outputPathModels = resolve(outputPath, "models") + const outputPathSchemas = resolve(outputPath, "schemas") + const outputPathServices = resolve(outputPath, "services") + const outputPathHooks = resolve(outputPath, "hooks") + + if (!isSubDirectory(process.cwd(), output)) { + throw new Error( + `Output folder is not a subdirectory of the current working directory` + ) + } + + if (exportCore) { + await rmdir(outputPathCore) + await mkdir(outputPathCore) + await writeClientCore( + client, + templates, + outputPathCore, + httpClient, + indent, + clientName, + request + ) + } + + if (exportServices) { + await rmdir(outputPathServices) + await mkdir(outputPathServices) + await writeClientServices( + client.services, + templates, + outputPathServices, + httpClient, + useUnionTypes, + useOptions, + indent, + postfixServices, + clientName, + packageNames + ) + await writeClientIndexServices( + client, + templates, + outputPathServices, + useUnionTypes, + exportCore, + exportServices, + exportModels, + exportHooks, + exportSchemas, + postfixServices, + postfixModels, + clientName + ) + } + + if (exportHooks) { + await rmdir(outputPathHooks) + await mkdir(outputPathHooks) + await writeClientHooks( + client.services, + templates, + outputPathHooks, + httpClient, + useUnionTypes, + useOptions, + indent, + postfixServices, + clientName, + packageNames + ) + await writeClientIndexHooks( + client, + templates, + outputPathHooks, + useUnionTypes, + exportCore, + exportServices, + exportModels, + exportHooks, + exportSchemas, + postfixServices, + postfixModels, + clientName + ) + } + + /** + * Deprecate. To remove. + */ + if (exportSchemas) { + await rmdir(outputPathSchemas) + await mkdir(outputPathSchemas) + await writeClientSchemas( + client.models, + templates, + outputPathSchemas, + httpClient, + useUnionTypes, + indent + ) + } + + if (exportModels) { + await rmdir(outputPathModels) + await mkdir(outputPathModels) + await writeClientModels( + client.models, + templates, + outputPathModels, + httpClient, + useUnionTypes, + indent + ) + await writeClientIndexModels( + client, + templates, + outputPathModels, + useUnionTypes, + exportCore, + exportServices, + exportModels, + exportHooks, + exportSchemas, + postfixServices, + postfixModels, + clientName + ) + } + + if (isDefined(clientName) && exportServices) { + await mkdir(outputPath) + await writeClientClass( + client, + templates, + outputPath, + httpClient, + clientName, + indent, + postfixServices + ) + } + + if (isDefined(clientName) && exportHooks) { + await mkdir(outputPath) + await mkdir(outputPathCore) + await writeFile( + resolve(outputPathCore, "HookUtils.ts"), + i(templates.core.hookUtils({}), indent) + ) + await writeUseClient( + client, + templates, + outputPath, + httpClient, + clientName, + indent, + postfixServices, + packageNames + ) + } + + if (exportCore || exportServices || exportSchemas || exportModels) { + await mkdir(outputPath) + await writeClientIndex( + client, + templates, + outputPath, + useUnionTypes, + exportCore, + exportServices, + exportModels, + exportHooks, + exportSchemas, + postfixServices, + postfixModels, + clientName + ) + } +} diff --git a/packages/oas/openapi-typescript-codegen/src/utils/writeClientClass.ts b/packages/oas/openapi-typescript-codegen/src/utils/writeClientClass.ts new file mode 100644 index 0000000000..685ab185c3 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/utils/writeClientClass.ts @@ -0,0 +1,50 @@ +import { resolve } from "path" + +import type { Client } from "../client/interfaces/Client" +import type { HttpClient } from "../HttpClient" +import type { Indent } from "../Indent" +import { writeFile } from "./fileSystem" +import { formatCode as f } from "./formatCode" +import { formatIndentation as i } from "./formatIndentation" +import { getHttpRequestName } from "./getHttpRequestName" +import type { Templates } from "./registerHandlebarTemplates" +import { sortModelsByName } from "./sortModelsByName" +import { sortServicesByName } from "./sortServicesByName" + +/** + * Generate the OpenAPI client index file using the Handlebar template and write it to disk. + * The index file just contains all the exports you need to use the client as a standalone + * library. But yuo can also import individual models and services directly. + * @param client Client object, containing, models, schemas and services + * @param templates The loaded handlebar templates + * @param outputPath Directory to write the generated files to + * @param httpClient The selected httpClient (fetch, xhr, node or axios) + * @param clientName Custom client class name + * @param indent Indentation options (4, 2 or tab) + * @param postfix Service name postfix + */ +export const writeClientClass = async ( + client: Client, + templates: Templates, + outputPath: string, + httpClient: HttpClient, + clientName: string, + indent: Indent, + postfix: string +): Promise => { + const templateResult = templates.client({ + clientName, + httpClient, + postfix, + server: client.server, + version: client.version, + models: sortModelsByName(client.models), + services: sortServicesByName(client.services), + httpRequest: getHttpRequestName(httpClient), + }) + + await writeFile( + resolve(outputPath, `${clientName}.ts`), + i(f(templateResult), indent) + ) +} diff --git a/packages/oas/openapi-typescript-codegen/src/utils/writeClientCore.ts b/packages/oas/openapi-typescript-codegen/src/utils/writeClientCore.ts new file mode 100644 index 0000000000..3e17ef8349 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/utils/writeClientCore.ts @@ -0,0 +1,84 @@ +import { resolve } from "path" + +import type { Client } from "../client/interfaces/Client" +import type { HttpClient } from "../HttpClient" +import type { Indent } from "../Indent" +import { copyFile, exists, writeFile } from "./fileSystem" +import { formatIndentation as i } from "./formatIndentation" +import { getHttpRequestName } from "./getHttpRequestName" +import { isDefined } from "./isDefined" +import type { Templates } from "./registerHandlebarTemplates" + +/** + * Generate OpenAPI core files, this includes the basic boilerplate code to handle requests. + * @param client Client object, containing, models, schemas and services + * @param templates The loaded handlebar templates + * @param outputPath Directory to write the generated files to + * @param httpClient The selected httpClient (fetch, xhr, node or axios) + * @param indent Indentation options (4, 2 or tab) + * @param clientName Custom client class name + * @param request Path to custom request file + */ +export const writeClientCore = async ( + client: Client, + templates: Templates, + outputPath: string, + httpClient: HttpClient, + indent: Indent, + clientName?: string, + request?: string +): Promise => { + const httpRequest = getHttpRequestName(httpClient) + const context = { + httpClient, + clientName, + httpRequest, + server: client.server, + version: client.version, + } + + await writeFile( + resolve(outputPath, "OpenAPI.ts"), + i(templates.core.settings(context), indent) + ) + await writeFile( + resolve(outputPath, "ApiError.ts"), + i(templates.core.apiError(context), indent) + ) + await writeFile( + resolve(outputPath, "ApiRequestOptions.ts"), + i(templates.core.apiRequestOptions(context), indent) + ) + await writeFile( + resolve(outputPath, "ApiResult.ts"), + i(templates.core.apiResult(context), indent) + ) + await writeFile( + resolve(outputPath, "CancelablePromise.ts"), + i(templates.core.cancelablePromise(context), indent) + ) + await writeFile( + resolve(outputPath, "request.ts"), + i(templates.core.request(context), indent) + ) + + if (isDefined(clientName)) { + await writeFile( + resolve(outputPath, "BaseHttpRequest.ts"), + i(templates.core.baseHttpRequest(context), indent) + ) + await writeFile( + resolve(outputPath, `${httpRequest}.ts`), + i(templates.core.httpRequest(context), indent) + ) + } + + if (request) { + const requestFile = resolve(process.cwd(), request) + const requestFileExists = await exists(requestFile) + if (!requestFileExists) { + throw new Error(`Custom request file "${requestFile}" does not exists`) + } + await copyFile(requestFile, resolve(outputPath, "request.ts")) + } +} diff --git a/packages/oas/openapi-typescript-codegen/src/utils/writeClientHooks.ts b/packages/oas/openapi-typescript-codegen/src/utils/writeClientHooks.ts new file mode 100644 index 0000000000..7c9f2f15d1 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/utils/writeClientHooks.ts @@ -0,0 +1,50 @@ +import { resolve } from "path" + +import type { Service } from "../client/interfaces/Service" +import type { HttpClient } from "../HttpClient" +import type { Indent } from "../Indent" +import { writeFile } from "./fileSystem" +import { formatCode as f } from "./formatCode" +import { formatIndentation as i } from "./formatIndentation" +import type { Templates } from "./registerHandlebarTemplates" +import { PackageNames } from "../index" + +/** + * Generate Services using the Handlebar template and write to disk. + * @param services Array of Services to write + * @param templates The loaded handlebar templates + * @param outputPath Directory to write the generated files to + * @param httpClient The selected httpClient (fetch, xhr, node or axios) + * @param useUnionTypes Use union types instead of enums + * @param useOptions Use options or arguments functions + * @param indent Indentation options (4, 2 or tab) + * @param postfixServices Service name postfix + * @param clientName Custom client class name + * @param packageNames Package name to use in import statements. + */ +export const writeClientHooks = async ( + services: Service[], + templates: Templates, + outputPath: string, + httpClient: HttpClient, + useUnionTypes: boolean, + useOptions: boolean, + indent: Indent, + postfixServices: string, + clientName?: string, + packageNames: PackageNames = {} +): Promise => { + for (const service of services) { + const file = resolve(outputPath, `use${service.name}.ts`) + const templateResult = templates.exports.hook({ + ...service, + httpClient, + useUnionTypes, + useOptions, + postfixServices, + clientName, + packageNames, + }) + await writeFile(file, i(f(templateResult), indent)) + } +} diff --git a/packages/oas/openapi-typescript-codegen/src/utils/writeClientIndex.ts b/packages/oas/openapi-typescript-codegen/src/utils/writeClientIndex.ts new file mode 100644 index 0000000000..485675ec74 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/utils/writeClientIndex.ts @@ -0,0 +1,161 @@ +import { resolve } from "path" + +import type { Client } from "../client/interfaces/Client" +import { writeFile } from "./fileSystem" +import { isDefined } from "./isDefined" +import { Templates } from "./registerHandlebarTemplates" +import { sortModelsByName } from "./sortModelsByName" +import { sortServicesByName } from "./sortServicesByName" + +/** + * Generate the OpenAPI client index file using the Handlebar template and write it to disk. + * The index file just contains all the exports you need to use the client as a standalone + * library. But yuo can also import individual models and services directly. + * @param client Client object, containing, models, schemas and services + * @param templates The loaded handlebar templates + * @param outputPath Directory to write the generated files to + * @param useUnionTypes Use union types instead of enums + * @param exportCore Generate core + * @param exportServices Generate services + * @param exportModels Generate models + * @param exportHooks Generate hooks + * @param exportSchemas Generate schemas + * @param postfixServices Service name postfix + * @param postfixModels Model name postfix + * @param clientName Custom client class name + */ +export const writeClientIndex = async ( + client: Client, + templates: Templates, + outputPath: string, + useUnionTypes: boolean, + exportCore: boolean, + exportServices: boolean, + exportModels: boolean, + exportHooks: boolean, + exportSchemas: boolean, + postfixServices: string, + postfixModels: string, + clientName?: string +): Promise => { + const templateResult = templates.indexes.index({ + exportCore, + exportServices, + exportModels, + exportHooks, + exportSchemas, + useUnionTypes, + postfixServices, + postfixModels, + clientName, + server: client.server, + version: client.version, + models: sortModelsByName(client.models), + services: sortServicesByName(client.services), + exportClient: isDefined(clientName) && exportServices, + }) + + await writeFile(resolve(outputPath, "index.ts"), templateResult) +} + +export const writeClientIndexModels = async ( + client: Client, + templates: Templates, + outputPath: string, + useUnionTypes: boolean, + exportCore: boolean, + exportServices: boolean, + exportModels: boolean, + exportHooks: boolean, + exportSchemas: boolean, + postfixServices: string, + postfixModels: string, + clientName?: string +): Promise => { + const templateResult = templates.indexes.indexModels({ + exportCore, + exportServices, + exportModels, + exportHooks, + exportSchemas, + useUnionTypes, + postfixServices, + postfixModels, + clientName, + server: client.server, + version: client.version, + models: sortModelsByName(client.models), + services: sortServicesByName(client.services), + exportClient: isDefined(clientName), + }) + + await writeFile(resolve(outputPath, "index.ts"), templateResult) +} + +export const writeClientIndexServices = async ( + client: Client, + templates: Templates, + outputPath: string, + useUnionTypes: boolean, + exportCore: boolean, + exportServices: boolean, + exportModels: boolean, + exportHooks: boolean, + exportSchemas: boolean, + postfixServices: string, + postfixModels: string, + clientName?: string +): Promise => { + const templateResult = templates.indexes.indexServices({ + exportCore, + exportServices, + exportModels, + exportHooks, + exportSchemas, + useUnionTypes, + postfixServices, + postfixModels, + clientName, + server: client.server, + version: client.version, + models: sortModelsByName(client.models), + services: sortServicesByName(client.services), + exportClient: isDefined(clientName), + }) + + await writeFile(resolve(outputPath, "index.ts"), templateResult) +} + +export const writeClientIndexHooks = async ( + client: Client, + templates: Templates, + outputPath: string, + useUnionTypes: boolean, + exportCore: boolean, + exportServices: boolean, + exportModels: boolean, + exportHooks: boolean, + exportSchemas: boolean, + postfixServices: string, + postfixModels: string, + clientName?: string +): Promise => { + const templateResult = templates.indexes.indexHooks({ + exportCore, + exportServices, + exportModels, + exportHooks, + exportSchemas, + useUnionTypes, + postfixServices, + postfixModels, + clientName, + server: client.server, + version: client.version, + models: sortModelsByName(client.models), + services: sortServicesByName(client.services), + exportClient: isDefined(clientName), + }) + + await writeFile(resolve(outputPath, "index.ts"), templateResult) +} diff --git a/packages/oas/openapi-typescript-codegen/src/utils/writeClientModels.ts b/packages/oas/openapi-typescript-codegen/src/utils/writeClientModels.ts new file mode 100644 index 0000000000..7a8707a6aa --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/utils/writeClientModels.ts @@ -0,0 +1,37 @@ +import { resolve } from "path" + +import type { Model } from "../client/interfaces/Model" +import type { HttpClient } from "../HttpClient" +import type { Indent } from "../Indent" +import { writeFile } from "./fileSystem" +import { formatCode as f } from "./formatCode" +import { formatIndentation as i } from "./formatIndentation" +import type { Templates } from "./registerHandlebarTemplates" + +/** + * Generate Models using the Handlebar template and write to disk. + * @param models Array of Models to write + * @param templates The loaded handlebar templates + * @param outputPath Directory to write the generated files to + * @param httpClient The selected httpClient (fetch, xhr, node or axios) + * @param useUnionTypes Use union types instead of enums + * @param indent Indentation options (4, 2 or tab) + */ +export const writeClientModels = async ( + models: Model[], + templates: Templates, + outputPath: string, + httpClient: HttpClient, + useUnionTypes: boolean, + indent: Indent +): Promise => { + for (const model of models) { + const file = resolve(outputPath, `${model.name}.ts`) + const templateResult = templates.exports.model({ + ...model, + httpClient, + useUnionTypes, + }) + await writeFile(file, i(f(templateResult), indent)) + } +} diff --git a/packages/oas/openapi-typescript-codegen/src/utils/writeClientSchemas.ts b/packages/oas/openapi-typescript-codegen/src/utils/writeClientSchemas.ts new file mode 100644 index 0000000000..111df7851a --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/utils/writeClientSchemas.ts @@ -0,0 +1,37 @@ +import { resolve } from "path" + +import type { Model } from "../client/interfaces/Model" +import type { HttpClient } from "../HttpClient" +import type { Indent } from "../Indent" +import { writeFile } from "./fileSystem" +import { formatCode as f } from "./formatCode" +import { formatIndentation as i } from "./formatIndentation" +import type { Templates } from "./registerHandlebarTemplates" + +/** + * Generate Schemas using the Handlebar template and write to disk. + * @param models Array of Models to write + * @param templates The loaded handlebar templates + * @param outputPath Directory to write the generated files to + * @param httpClient The selected httpClient (fetch, xhr, node or axios) + * @param useUnionTypes Use union types instead of enums + * @param indent Indentation options (4, 2 or tab) + */ +export const writeClientSchemas = async ( + models: Model[], + templates: Templates, + outputPath: string, + httpClient: HttpClient, + useUnionTypes: boolean, + indent: Indent +): Promise => { + for (const model of models) { + const file = resolve(outputPath, `$${model.name}.ts`) + const templateResult = templates.exports.schema({ + ...model, + httpClient, + useUnionTypes, + }) + await writeFile(file, i(f(templateResult), indent)) + } +} diff --git a/packages/oas/openapi-typescript-codegen/src/utils/writeClientServices.ts b/packages/oas/openapi-typescript-codegen/src/utils/writeClientServices.ts new file mode 100644 index 0000000000..4e055038e5 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/utils/writeClientServices.ts @@ -0,0 +1,51 @@ +import { resolve } from "path" + +import type { Service } from "../client/interfaces/Service" +import type { HttpClient } from "../HttpClient" +import type { Indent } from "../Indent" +import { writeFile } from "./fileSystem" +import { formatCode as f } from "./formatCode" +import { formatIndentation as i } from "./formatIndentation" +import { isDefined } from "./isDefined" +import type { Templates } from "./registerHandlebarTemplates" +import { PackageNames } from "../index" + +/** + * Generate Services using the Handlebar template and write to disk. + * @param services Array of Services to write + * @param templates The loaded handlebar templates + * @param outputPath Directory to write the generated files to + * @param httpClient The selected httpClient (fetch, xhr, node or axios) + * @param useUnionTypes Use union types instead of enums + * @param useOptions Use options or arguments functions + * @param indent Indentation options (4, 2 or tab) + * @param postfix Service name postfix + * @param clientName Custom client class name + * @param packageNames Package name to use in import statements. + */ +export const writeClientServices = async ( + services: Service[], + templates: Templates, + outputPath: string, + httpClient: HttpClient, + useUnionTypes: boolean, + useOptions: boolean, + indent: Indent, + postfix: string, + clientName?: string, + packageNames: PackageNames = {} +): Promise => { + for (const service of services) { + const file = resolve(outputPath, `${service.name}${postfix}.ts`) + const templateResult = templates.exports.service({ + ...service, + httpClient, + useUnionTypes, + useOptions, + postfix, + exportClient: isDefined(clientName), + packageNames, + }) + await writeFile(file, i(f(templateResult), indent)) + } +} diff --git a/packages/oas/openapi-typescript-codegen/src/utils/writeUseClient.ts b/packages/oas/openapi-typescript-codegen/src/utils/writeUseClient.ts new file mode 100644 index 0000000000..76491d76e6 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/src/utils/writeUseClient.ts @@ -0,0 +1,51 @@ +import { resolve } from "path" + +import type { Client } from "../client/interfaces/Client" +import type { HttpClient } from "../HttpClient" +import type { Indent } from "../Indent" +import { writeFile } from "./fileSystem" +import { formatCode as f } from "./formatCode" +import { formatIndentation as i } from "./formatIndentation" +import { getHttpRequestName } from "./getHttpRequestName" +import type { Templates } from "./registerHandlebarTemplates" +import { sortModelsByName } from "./sortModelsByName" +import { sortServicesByName } from "./sortServicesByName" +import camelCase from "camelcase" +import { PackageNames } from "../index" + +/** + * Generate context for hooks. + * @param client Client object, containing, models, schemas and services + * @param templates The loaded handlebar templates + * @param outputPath Directory to write the generated files to + * @param httpClient The selected httpClient (fetch, xhr, node or axios) + * @param clientName Custom client class name + * @param indent Indentation options (4, 2 or tab) + * @param postfix Service name postfix + * @param packageNames Package name to use in import statements. + */ +export const writeUseClient = async ( + client: Client, + templates: Templates, + outputPath: string, + httpClient: HttpClient, + clientName: string, + indent: Indent, + postfix: string, + packageNames: PackageNames = {} +): Promise => { + const templateResult = templates.useClient({ + clientName, + httpClient, + postfix, + server: client.server, + version: client.version, + models: sortModelsByName(client.models), + services: sortServicesByName(client.services), + httpRequest: getHttpRequestName(httpClient), + packageNames, + }) + + const filename = `use${camelCase(clientName, { pascalCase: true })}.tsx` + await writeFile(resolve(outputPath, filename), i(f(templateResult), indent)) +} diff --git a/packages/oas/openapi-typescript-codegen/tsconfig.json b/packages/oas/openapi-typescript-codegen/tsconfig.json new file mode 100644 index 0000000000..293c548bdd --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "outDir": "./dist", + "target": "es2019", + "module": "commonjs", + "moduleResolution": "node", + "lib": ["es2019", "dom"], + "types": ["jest", "node"], + "declaration": false, + "declarationMap": false, + "sourceMap": false, + "noImplicitReturns": true, + "noImplicitThis": true, + "noImplicitAny": true, + "strict": true, + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "experimentalDecorators": true + }, + + "files": ["./src/typings/hbs.d.ts"], + + "include": ["./src/**/*.ts"], + + "exclude": ["node_modules", "**/__mocks__"] +} diff --git a/packages/oas/openapi-typescript-codegen/types/index.d.ts b/packages/oas/openapi-typescript-codegen/types/index.d.ts new file mode 100644 index 0000000000..62cd066e76 --- /dev/null +++ b/packages/oas/openapi-typescript-codegen/types/index.d.ts @@ -0,0 +1,48 @@ +export declare enum HttpClient { + FETCH = "fetch", + XHR = "xhr", + NODE = "node", + AXIOS = "axios", + ANGULAR = "angular", +} + +export declare enum Indent { + SPACE_4 = "4", + SPACE_2 = "2", + TAB = "tab", +} + +export type PackageNames = { + models?: string + client?: string +} + +export type Options = { + input: string | Record + output: string + httpClient?: HttpClient | "fetch" | "xhr" | "node" | "axios" | "angular" + clientName?: string + useOptions?: boolean + useUnionTypes?: boolean + exportCore?: boolean + exportServices?: boolean + exportModels?: boolean + exportHooks?: boolean + exportSchemas?: boolean + indent?: Indent | "4" | "2" | "tab" + packageNames?: PackageNames + postfixServices?: string + postfixModels?: string + request?: string + write?: boolean +} + +export declare function generate(options: Options): Promise + +declare type OpenAPI = { + HttpClient: HttpClient + Indent: Indent + generate: typeof generate +} + +export default OpenAPI diff --git a/yarn.lock b/yarn.lock index 62a834ef29..89011e611e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -157,6 +157,18 @@ __metadata: languageName: node linkType: hard +"@apidevtools/json-schema-ref-parser@npm:9.0.9": + version: 9.0.9 + resolution: "@apidevtools/json-schema-ref-parser@npm:9.0.9" + dependencies: + "@jsdevtools/ono": ^7.1.3 + "@types/json-schema": ^7.0.6 + call-me-maybe: ^1.0.1 + js-yaml: ^4.1.0 + checksum: d1457e57ca12abcd3da25b9a2cb2982267102c65aefeaf284a3321b818090b9032d6b7a3ad9625abc00d07f5b1432d643f79aa6f515c1a787523d40563ef85e4 + languageName: node + linkType: hard + "@apidevtools/openapi-schemas@npm:^2.1.0": version: 2.1.0 resolution: "@apidevtools/openapi-schemas@npm:2.1.0" @@ -216,6 +228,33 @@ __metadata: languageName: node linkType: hard +"@babel/cli@npm:7.14.3": + version: 7.14.3 + resolution: "@babel/cli@npm:7.14.3" + dependencies: + "@nicolo-ribaudo/chokidar-2": 2.1.8-no-fsevents + chokidar: ^3.4.0 + commander: ^4.0.1 + convert-source-map: ^1.1.0 + fs-readdir-recursive: ^1.1.0 + glob: ^7.0.0 + make-dir: ^2.1.0 + slash: ^2.0.0 + source-map: ^0.5.0 + peerDependencies: + "@babel/core": ^7.0.0-0 + dependenciesMeta: + "@nicolo-ribaudo/chokidar-2": + optional: true + chokidar: + optional: true + bin: + babel: ./bin/babel.js + babel-external-helpers: ./bin/babel-external-helpers.js + checksum: fb314827536ac00d22516c376d5ec36c14219c615406dd35ea3326034675ac90f86a52a9a7ca03901cf3e3cd5c902902894a4ecaa1de1204542f69dca8074f8e + languageName: node + linkType: hard + "@babel/cli@npm:^7.12.1, @babel/cli@npm:^7.14.3, @babel/cli@npm:^7.15.4, @babel/cli@npm:^7.16.0, @babel/cli@npm:^7.7.5": version: 7.18.6 resolution: "@babel/cli@npm:7.18.6" @@ -288,6 +327,13 @@ __metadata: languageName: node linkType: hard +"@babel/compat-data@npm:^7.11.0, @babel/compat-data@npm:^7.20.5": + version: 7.20.14 + resolution: "@babel/compat-data@npm:7.20.14" + checksum: b35587fe2f90dbf4e07d33fcaaa49fa117313eeb892591fede7679b21f7aff4235735a709fdb771a9a33b9e57d5cebed522108ad1364f6a1abf91cf16ffde1e4 + languageName: node + linkType: hard + "@babel/compat-data@npm:^7.13.11, @babel/compat-data@npm:^7.18.6": version: 7.18.8 resolution: "@babel/compat-data@npm:7.18.8" @@ -295,13 +341,6 @@ __metadata: languageName: node linkType: hard -"@babel/compat-data@npm:^7.20.5": - version: 7.20.14 - resolution: "@babel/compat-data@npm:7.20.14" - checksum: b35587fe2f90dbf4e07d33fcaaa49fa117313eeb892591fede7679b21f7aff4235735a709fdb771a9a33b9e57d5cebed522108ad1364f6a1abf91cf16ffde1e4 - languageName: node - linkType: hard - "@babel/core@npm:7.12.9": version: 7.12.9 resolution: "@babel/core@npm:7.12.9" @@ -326,6 +365,29 @@ __metadata: languageName: node linkType: hard +"@babel/core@npm:7.14.3": + version: 7.14.3 + resolution: "@babel/core@npm:7.14.3" + dependencies: + "@babel/code-frame": ^7.12.13 + "@babel/generator": ^7.14.3 + "@babel/helper-compilation-targets": ^7.13.16 + "@babel/helper-module-transforms": ^7.14.2 + "@babel/helpers": ^7.14.0 + "@babel/parser": ^7.14.3 + "@babel/template": ^7.12.13 + "@babel/traverse": ^7.14.2 + "@babel/types": ^7.14.2 + convert-source-map: ^1.7.0 + debug: ^4.1.0 + gensync: ^1.0.0-beta.2 + json5: ^2.1.2 + semver: ^6.3.0 + source-map: ^0.5.0 + checksum: c6bdfc5a76149de34ba414b327c1f69fb9b5902f4e999a4a6e21488585758365c94b1384c81e207baec743cfc07bbd139ca07f95c7dd4a831116e32d98121d1f + languageName: node + linkType: hard + "@babel/core@npm:^7.1.0, @babel/core@npm:^7.12.10, @babel/core@npm:^7.12.3, @babel/core@npm:^7.12.7, @babel/core@npm:^7.14.0, @babel/core@npm:^7.14.3, @babel/core@npm:^7.15.5, @babel/core@npm:^7.16.0, @babel/core@npm:^7.7.2, @babel/core@npm:^7.7.5, @babel/core@npm:^7.8.0": version: 7.18.6 resolution: "@babel/core@npm:7.18.6" @@ -397,7 +459,7 @@ __metadata: languageName: node linkType: hard -"@babel/generator@npm:^7.20.7": +"@babel/generator@npm:^7.14.3, @babel/generator@npm:^7.20.7": version: 7.20.14 resolution: "@babel/generator@npm:7.20.14" dependencies: @@ -427,21 +489,7 @@ __metadata: languageName: node linkType: hard -"@babel/helper-compilation-targets@npm:^7.13.0, @babel/helper-compilation-targets@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/helper-compilation-targets@npm:7.18.6" - dependencies: - "@babel/compat-data": ^7.18.6 - "@babel/helper-validator-option": ^7.18.6 - browserslist: ^4.20.2 - semver: ^6.3.0 - peerDependencies: - "@babel/core": ^7.0.0 - checksum: c116f755e78259b646540e3f6f6b7b9a0dd9537fdc8f4bba28c330541e90fac609adaed4d2e4ef7f915e1aa4c3d802767a6564ac00fe4ded77c499c96b87d9b5 - languageName: node - linkType: hard - -"@babel/helper-compilation-targets@npm:^7.20.7": +"@babel/helper-compilation-targets@npm:^7.10.4, @babel/helper-compilation-targets@npm:^7.13.16, @babel/helper-compilation-targets@npm:^7.18.9, @babel/helper-compilation-targets@npm:^7.20.7": version: 7.20.7 resolution: "@babel/helper-compilation-targets@npm:7.20.7" dependencies: @@ -456,6 +504,20 @@ __metadata: languageName: node linkType: hard +"@babel/helper-compilation-targets@npm:^7.13.0, @babel/helper-compilation-targets@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/helper-compilation-targets@npm:7.18.6" + dependencies: + "@babel/compat-data": ^7.18.6 + "@babel/helper-validator-option": ^7.18.6 + browserslist: ^4.20.2 + semver: ^6.3.0 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: c116f755e78259b646540e3f6f6b7b9a0dd9537fdc8f4bba28c330541e90fac609adaed4d2e4ef7f915e1aa4c3d802767a6564ac00fe4ded77c499c96b87d9b5 + languageName: node + linkType: hard + "@babel/helper-create-class-features-plugin@npm:^7.12.1, @babel/helper-create-class-features-plugin@npm:^7.18.6": version: 7.18.6 resolution: "@babel/helper-create-class-features-plugin@npm:7.18.6" @@ -485,6 +547,18 @@ __metadata: languageName: node linkType: hard +"@babel/helper-create-regexp-features-plugin@npm:^7.20.5": + version: 7.20.5 + resolution: "@babel/helper-create-regexp-features-plugin@npm:7.20.5" + dependencies: + "@babel/helper-annotate-as-pure": ^7.18.6 + regexpu-core: ^5.2.1 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 567132405fc79cd97a656a966d97a76d22cb05dd82b9293952f51ba849b849ba829cf6715bc7c8aa3f3510e1b5aaa798e3216cd92a612e353004c55a407b35cd + languageName: node + linkType: hard + "@babel/helper-define-polyfill-provider@npm:^0.1.5": version: 0.1.5 resolution: "@babel/helper-define-polyfill-provider@npm:0.1.5" @@ -554,7 +628,7 @@ __metadata: languageName: node linkType: hard -"@babel/helper-function-name@npm:^7.19.0": +"@babel/helper-function-name@npm:^7.18.9, @babel/helper-function-name@npm:^7.19.0": version: 7.19.0 resolution: "@babel/helper-function-name@npm:7.19.0" dependencies: @@ -607,7 +681,7 @@ __metadata: languageName: node linkType: hard -"@babel/helper-module-transforms@npm:^7.20.11": +"@babel/helper-module-transforms@npm:^7.14.2, @babel/helper-module-transforms@npm:^7.20.11": version: 7.20.11 resolution: "@babel/helper-module-transforms@npm:7.20.11" dependencies: @@ -646,6 +720,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-plugin-utils@npm:^7.18.9, @babel/helper-plugin-utils@npm:^7.20.2": + version: 7.20.2 + resolution: "@babel/helper-plugin-utils@npm:7.20.2" + checksum: bf4de040e57b7ddff36ea599e963c391eb246d5a95207bb9ef3e33073c451bcc0821e3a9cc08dfede862a6dcc110d7e6e7d9a483482f852be358c5b60add499c + languageName: node + linkType: hard + "@babel/helper-remap-async-to-generator@npm:^7.18.6": version: 7.18.6 resolution: "@babel/helper-remap-async-to-generator@npm:7.18.6" @@ -660,6 +741,20 @@ __metadata: languageName: node linkType: hard +"@babel/helper-remap-async-to-generator@npm:^7.18.9": + version: 7.18.9 + resolution: "@babel/helper-remap-async-to-generator@npm:7.18.9" + dependencies: + "@babel/helper-annotate-as-pure": ^7.18.6 + "@babel/helper-environment-visitor": ^7.18.9 + "@babel/helper-wrap-function": ^7.18.9 + "@babel/types": ^7.18.9 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: e6b2a906bdb3ec40d9cee7b7f8d02a561334603a0c57406a37c77d301ca77412ff33f2cef9d89421d7c3b1359604d613c596621a2ff22129612213198c5d1527 + languageName: node + linkType: hard + "@babel/helper-replace-supers@npm:^7.18.6": version: 7.18.6 resolution: "@babel/helper-replace-supers@npm:7.18.6" @@ -700,6 +795,15 @@ __metadata: languageName: node linkType: hard +"@babel/helper-skip-transparent-expression-wrappers@npm:^7.20.0": + version: 7.20.0 + resolution: "@babel/helper-skip-transparent-expression-wrappers@npm:7.20.0" + dependencies: + "@babel/types": ^7.20.0 + checksum: 8529fb760ffbc3efc22ec5a079039fae65f40a90e9986642a85c1727aabdf6a79929546412f6210593970d2f97041f73bdd316e481d61110d6edcac1f97670a9 + languageName: node + linkType: hard + "@babel/helper-split-export-declaration@npm:^7.18.6": version: 7.18.6 resolution: "@babel/helper-split-export-declaration@npm:7.18.6" @@ -749,6 +853,18 @@ __metadata: languageName: node linkType: hard +"@babel/helper-wrap-function@npm:^7.18.9": + version: 7.20.5 + resolution: "@babel/helper-wrap-function@npm:7.20.5" + dependencies: + "@babel/helper-function-name": ^7.19.0 + "@babel/template": ^7.18.10 + "@babel/traverse": ^7.20.5 + "@babel/types": ^7.20.5 + checksum: b5ea154778f6dbeb3cb9917933ea364f8f643aa79665c51f4a4b903bc451b3d18a738ab9952bdb43a81647f301a9be305bfcf02f2222b1235197e52c525703d6 + languageName: node + linkType: hard + "@babel/helpers@npm:^7.12.5, @babel/helpers@npm:^7.18.6": version: 7.18.6 resolution: "@babel/helpers@npm:7.18.6" @@ -760,7 +876,7 @@ __metadata: languageName: node linkType: hard -"@babel/helpers@npm:^7.20.7": +"@babel/helpers@npm:^7.14.0, @babel/helpers@npm:^7.20.7": version: 7.20.13 resolution: "@babel/helpers@npm:7.20.13" dependencies: @@ -827,6 +943,15 @@ __metadata: languageName: node linkType: hard +"@babel/parser@npm:^7.14.3": + version: 7.20.15 + resolution: "@babel/parser@npm:7.20.15" + bin: + parser: ./bin/babel-parser.js + checksum: 6bea1cedd1c783451984e3c9156052b88f194345ffbfac91e739cbd0d2a7ecb4b46fb027afa4b655d15eed4d0743105e960d93eb3ccc067e24fa2b39e8643861 + languageName: node + linkType: hard + "@babel/parser@npm:^7.20.13, @babel/parser@npm:^7.20.7": version: 7.20.13 resolution: "@babel/parser@npm:7.20.13" @@ -860,6 +985,20 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-proposal-async-generator-functions@npm:^7.10.4": + version: 7.20.7 + resolution: "@babel/plugin-proposal-async-generator-functions@npm:7.20.7" + dependencies: + "@babel/helper-environment-visitor": ^7.18.9 + "@babel/helper-plugin-utils": ^7.20.2 + "@babel/helper-remap-async-to-generator": ^7.18.9 + "@babel/plugin-syntax-async-generators": ^7.8.4 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 0f4bc01805704ae4840536acc9888c50a32250e9188d025063bd17fe77ed171a12361c3dc83ce99664dcd73aec612accb8da95b0d8b825c854931b2860c0bfb5 + languageName: node + linkType: hard + "@babel/plugin-proposal-async-generator-functions@npm:^7.18.6": version: 7.18.6 resolution: "@babel/plugin-proposal-async-generator-functions@npm:7.18.6" @@ -926,7 +1065,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-proposal-dynamic-import@npm:^7.18.6": +"@babel/plugin-proposal-dynamic-import@npm:^7.10.4, @babel/plugin-proposal-dynamic-import@npm:^7.18.6": version: 7.18.6 resolution: "@babel/plugin-proposal-dynamic-import@npm:7.18.6" dependencies: @@ -950,6 +1089,18 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-proposal-export-namespace-from@npm:^7.10.4": + version: 7.18.9 + resolution: "@babel/plugin-proposal-export-namespace-from@npm:7.18.9" + dependencies: + "@babel/helper-plugin-utils": ^7.18.9 + "@babel/plugin-syntax-export-namespace-from": ^7.8.3 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: b90346bd3628ebd44138d0628a5aba1e6b11748893fb48e87008cac30f3bc7cd3161362e49433156737350318174164436357a66fbbfdbe952606b460bd8a0e4 + languageName: node + linkType: hard + "@babel/plugin-proposal-export-namespace-from@npm:^7.18.6": version: 7.18.6 resolution: "@babel/plugin-proposal-export-namespace-from@npm:7.18.6" @@ -962,7 +1113,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-proposal-json-strings@npm:^7.18.6": +"@babel/plugin-proposal-json-strings@npm:^7.10.4, @babel/plugin-proposal-json-strings@npm:^7.18.6": version: 7.18.6 resolution: "@babel/plugin-proposal-json-strings@npm:7.18.6" dependencies: @@ -974,6 +1125,18 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-proposal-logical-assignment-operators@npm:^7.11.0": + version: 7.20.7 + resolution: "@babel/plugin-proposal-logical-assignment-operators@npm:7.20.7" + dependencies: + "@babel/helper-plugin-utils": ^7.20.2 + "@babel/plugin-syntax-logical-assignment-operators": ^7.10.4 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 436c1ee9f983813fc52788980a7231414351bd34d80b16b83bddb09115386292fe4912cc6d172304eabbaf0c4813625331b9b5bc798acb0e8925cf0d2b394d4d + languageName: node + linkType: hard + "@babel/plugin-proposal-logical-assignment-operators@npm:^7.18.6": version: 7.18.6 resolution: "@babel/plugin-proposal-logical-assignment-operators@npm:7.18.6" @@ -986,7 +1149,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-proposal-nullish-coalescing-operator@npm:^7.12.1, @babel/plugin-proposal-nullish-coalescing-operator@npm:^7.14.5, @babel/plugin-proposal-nullish-coalescing-operator@npm:^7.18.6": +"@babel/plugin-proposal-nullish-coalescing-operator@npm:^7.10.4, @babel/plugin-proposal-nullish-coalescing-operator@npm:^7.12.1, @babel/plugin-proposal-nullish-coalescing-operator@npm:^7.14.5, @babel/plugin-proposal-nullish-coalescing-operator@npm:^7.18.6": version: 7.18.6 resolution: "@babel/plugin-proposal-nullish-coalescing-operator@npm:7.18.6" dependencies: @@ -998,7 +1161,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-proposal-numeric-separator@npm:^7.14.5, @babel/plugin-proposal-numeric-separator@npm:^7.18.6": +"@babel/plugin-proposal-numeric-separator@npm:^7.10.4, @babel/plugin-proposal-numeric-separator@npm:^7.14.5, @babel/plugin-proposal-numeric-separator@npm:^7.18.6": version: 7.18.6 resolution: "@babel/plugin-proposal-numeric-separator@npm:7.18.6" dependencies: @@ -1038,7 +1201,22 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-proposal-optional-catch-binding@npm:^7.18.6": +"@babel/plugin-proposal-object-rest-spread@npm:^7.11.0": + version: 7.20.7 + resolution: "@babel/plugin-proposal-object-rest-spread@npm:7.20.7" + dependencies: + "@babel/compat-data": ^7.20.5 + "@babel/helper-compilation-targets": ^7.20.7 + "@babel/helper-plugin-utils": ^7.20.2 + "@babel/plugin-syntax-object-rest-spread": ^7.8.3 + "@babel/plugin-transform-parameters": ^7.20.7 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: b9818749bb49d8095df64c45db682448d04743d96722984cbfd375733b2585c26d807f84b4fdb28474f2d614be6a6ffe3d96ffb121840e9e5345b2ccc0438bd8 + languageName: node + linkType: hard + +"@babel/plugin-proposal-optional-catch-binding@npm:^7.10.4, @babel/plugin-proposal-optional-catch-binding@npm:^7.18.6": version: 7.18.6 resolution: "@babel/plugin-proposal-optional-catch-binding@npm:7.18.6" dependencies: @@ -1050,6 +1228,19 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-proposal-optional-chaining@npm:^7.11.0": + version: 7.20.7 + resolution: "@babel/plugin-proposal-optional-chaining@npm:7.20.7" + dependencies: + "@babel/helper-plugin-utils": ^7.20.2 + "@babel/helper-skip-transparent-expression-wrappers": ^7.20.0 + "@babel/plugin-syntax-optional-chaining": ^7.8.3 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 8aa2b9691a61e9780f05b5fc247a9b2944fa0f7841c575b459631cd72a828c4d8062bd12c60859409b4219198c291954e3a03bc570587235f6123728a23cc3ab + languageName: node + linkType: hard + "@babel/plugin-proposal-optional-chaining@npm:^7.12.7, @babel/plugin-proposal-optional-chaining@npm:^7.14.2, @babel/plugin-proposal-optional-chaining@npm:^7.14.5, @babel/plugin-proposal-optional-chaining@npm:^7.18.6": version: 7.18.6 resolution: "@babel/plugin-proposal-optional-chaining@npm:7.18.6" @@ -1063,7 +1254,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-proposal-private-methods@npm:^7.12.1, @babel/plugin-proposal-private-methods@npm:^7.18.6": +"@babel/plugin-proposal-private-methods@npm:^7.10.4, @babel/plugin-proposal-private-methods@npm:^7.12.1, @babel/plugin-proposal-private-methods@npm:^7.18.6": version: 7.18.6 resolution: "@babel/plugin-proposal-private-methods@npm:7.18.6" dependencies: @@ -1089,7 +1280,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-proposal-unicode-property-regex@npm:^7.18.6, @babel/plugin-proposal-unicode-property-regex@npm:^7.4.4": +"@babel/plugin-proposal-unicode-property-regex@npm:^7.10.4, @babel/plugin-proposal-unicode-property-regex@npm:^7.18.6, @babel/plugin-proposal-unicode-property-regex@npm:^7.4.4": version: 7.18.6 resolution: "@babel/plugin-proposal-unicode-property-regex@npm:7.18.6" dependencies: @@ -1101,7 +1292,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-async-generators@npm:^7.8.4": +"@babel/plugin-syntax-async-generators@npm:^7.8.0, @babel/plugin-syntax-async-generators@npm:^7.8.4": version: 7.8.4 resolution: "@babel/plugin-syntax-async-generators@npm:7.8.4" dependencies: @@ -1123,7 +1314,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-class-properties@npm:^7.0.0, @babel/plugin-syntax-class-properties@npm:^7.12.13, @babel/plugin-syntax-class-properties@npm:^7.8.3": +"@babel/plugin-syntax-class-properties@npm:^7.0.0, @babel/plugin-syntax-class-properties@npm:^7.10.4, @babel/plugin-syntax-class-properties@npm:^7.12.13, @babel/plugin-syntax-class-properties@npm:^7.8.3": version: 7.12.13 resolution: "@babel/plugin-syntax-class-properties@npm:7.12.13" dependencies: @@ -1156,7 +1347,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-dynamic-import@npm:^7.8.3": +"@babel/plugin-syntax-dynamic-import@npm:^7.8.0, @babel/plugin-syntax-dynamic-import@npm:^7.8.3": version: 7.8.3 resolution: "@babel/plugin-syntax-dynamic-import@npm:7.8.3" dependencies: @@ -1222,7 +1413,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-json-strings@npm:^7.8.3": +"@babel/plugin-syntax-json-strings@npm:^7.8.0, @babel/plugin-syntax-json-strings@npm:^7.8.3": version: 7.8.3 resolution: "@babel/plugin-syntax-json-strings@npm:7.8.3" dependencies: @@ -1266,7 +1457,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-nullish-coalescing-operator@npm:^7.8.3": +"@babel/plugin-syntax-nullish-coalescing-operator@npm:^7.8.0, @babel/plugin-syntax-nullish-coalescing-operator@npm:^7.8.3": version: 7.8.3 resolution: "@babel/plugin-syntax-nullish-coalescing-operator@npm:7.8.3" dependencies: @@ -1299,7 +1490,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-optional-catch-binding@npm:^7.8.3": +"@babel/plugin-syntax-optional-catch-binding@npm:^7.8.0, @babel/plugin-syntax-optional-catch-binding@npm:^7.8.3": version: 7.8.3 resolution: "@babel/plugin-syntax-optional-catch-binding@npm:7.8.3" dependencies: @@ -1310,7 +1501,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-optional-chaining@npm:^7.8.3": +"@babel/plugin-syntax-optional-chaining@npm:^7.8.0, @babel/plugin-syntax-optional-chaining@npm:^7.8.3": version: 7.8.3 resolution: "@babel/plugin-syntax-optional-chaining@npm:7.8.3" dependencies: @@ -1332,7 +1523,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-top-level-await@npm:^7.14.5, @babel/plugin-syntax-top-level-await@npm:^7.8.3": +"@babel/plugin-syntax-top-level-await@npm:^7.10.4, @babel/plugin-syntax-top-level-await@npm:^7.14.5, @babel/plugin-syntax-top-level-await@npm:^7.8.3": version: 7.14.5 resolution: "@babel/plugin-syntax-top-level-await@npm:7.14.5" dependencies: @@ -1365,6 +1556,30 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-arrow-functions@npm:^7.10.4": + version: 7.20.7 + resolution: "@babel/plugin-transform-arrow-functions@npm:7.20.7" + dependencies: + "@babel/helper-plugin-utils": ^7.20.2 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 690fc85afd273049f87e917ab75915e0c0ef19f62633d7d1706a1126dcfac9571d244b5b4eed9b64d6320a8560e8a6e17cf6ea38f4ecc6010e889953c1509b25 + languageName: node + linkType: hard + +"@babel/plugin-transform-async-to-generator@npm:^7.10.4": + version: 7.20.7 + resolution: "@babel/plugin-transform-async-to-generator@npm:7.20.7" + dependencies: + "@babel/helper-module-imports": ^7.18.6 + "@babel/helper-plugin-utils": ^7.20.2 + "@babel/helper-remap-async-to-generator": ^7.18.9 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: c98caeafbffbdb40fd5d9d4c7a835d624ba1ada814e8e675d99a9c83bd40780ab6a52e3b873e81dc7ce045a3990427073e634f07cc2f2681d780faee0717d7e9 + languageName: node + linkType: hard + "@babel/plugin-transform-async-to-generator@npm:^7.18.6": version: 7.18.6 resolution: "@babel/plugin-transform-async-to-generator@npm:7.18.6" @@ -1378,7 +1593,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-block-scoped-functions@npm:^7.0.0, @babel/plugin-transform-block-scoped-functions@npm:^7.18.6": +"@babel/plugin-transform-block-scoped-functions@npm:^7.0.0, @babel/plugin-transform-block-scoped-functions@npm:^7.10.4, @babel/plugin-transform-block-scoped-functions@npm:^7.18.6": version: 7.18.6 resolution: "@babel/plugin-transform-block-scoped-functions@npm:7.18.6" dependencies: @@ -1400,6 +1615,17 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-block-scoping@npm:^7.10.4": + version: 7.20.15 + resolution: "@babel/plugin-transform-block-scoping@npm:7.20.15" + dependencies: + "@babel/helper-plugin-utils": ^7.20.2 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 6cf805f08a87a9e70d19308154286522072f7ad1f6c106fba0f73dcc90674be5315fbbffee4f3040106331a9187fd76ba80e7cca4945ee8621713f28653e5e6f + languageName: node + linkType: hard + "@babel/plugin-transform-classes@npm:^7.0.0, @babel/plugin-transform-classes@npm:^7.10.4, @babel/plugin-transform-classes@npm:^7.12.1, @babel/plugin-transform-classes@npm:^7.15.4, @babel/plugin-transform-classes@npm:^7.16.0, @babel/plugin-transform-classes@npm:^7.18.6, @babel/plugin-transform-classes@npm:^7.9.5": version: 7.18.8 resolution: "@babel/plugin-transform-classes@npm:7.18.8" @@ -1429,6 +1655,18 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-computed-properties@npm:^7.10.4": + version: 7.20.7 + resolution: "@babel/plugin-transform-computed-properties@npm:7.20.7" + dependencies: + "@babel/helper-plugin-utils": ^7.20.2 + "@babel/template": ^7.20.7 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 849c11bac3600d8afa9f3a440fc721cdf2b719480b9a0b230849092fa400099ba1e91328e168860a2ca4d2843a94ece57a894b47468aaeb83df27bb82aae5d07 + languageName: node + linkType: hard + "@babel/plugin-transform-destructuring@npm:^7.0.0, @babel/plugin-transform-destructuring@npm:^7.12.1, @babel/plugin-transform-destructuring@npm:^7.18.6": version: 7.18.6 resolution: "@babel/plugin-transform-destructuring@npm:7.18.6" @@ -1440,7 +1678,18 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-dotall-regex@npm:^7.18.6, @babel/plugin-transform-dotall-regex@npm:^7.4.4": +"@babel/plugin-transform-destructuring@npm:^7.10.4": + version: 7.20.7 + resolution: "@babel/plugin-transform-destructuring@npm:7.20.7" + dependencies: + "@babel/helper-plugin-utils": ^7.20.2 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 747889ec3dfcd992b63d55faf598f152822df75cc6da299789695ef8dbe520c78a2f146152d646afd2805f9abe1c13045fd1b3ab97be5e0d6901c73ea4209c44 + languageName: node + linkType: hard + +"@babel/plugin-transform-dotall-regex@npm:^7.10.4, @babel/plugin-transform-dotall-regex@npm:^7.18.6, @babel/plugin-transform-dotall-regex@npm:^7.4.4": version: 7.18.6 resolution: "@babel/plugin-transform-dotall-regex@npm:7.18.6" dependencies: @@ -1452,6 +1701,17 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-duplicate-keys@npm:^7.10.4": + version: 7.18.9 + resolution: "@babel/plugin-transform-duplicate-keys@npm:7.18.9" + dependencies: + "@babel/helper-plugin-utils": ^7.18.9 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: dfb7f7e66c0c862d205fe8f8b87f7ac174549c56937a5186b6e6cf85358ce257115fec0aa55e78fde53e5132d5aae9383e81aba8a4b70faa0e9fb64e3a66ca96 + languageName: node + linkType: hard + "@babel/plugin-transform-duplicate-keys@npm:^7.18.6": version: 7.18.6 resolution: "@babel/plugin-transform-duplicate-keys@npm:7.18.6" @@ -1463,7 +1723,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-exponentiation-operator@npm:^7.18.6": +"@babel/plugin-transform-exponentiation-operator@npm:^7.10.4, @babel/plugin-transform-exponentiation-operator@npm:^7.18.6": version: 7.18.6 resolution: "@babel/plugin-transform-exponentiation-operator@npm:7.18.6" dependencies: @@ -1487,7 +1747,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-for-of@npm:^7.0.0, @babel/plugin-transform-for-of@npm:^7.12.1, @babel/plugin-transform-for-of@npm:^7.18.6": +"@babel/plugin-transform-for-of@npm:^7.0.0, @babel/plugin-transform-for-of@npm:^7.10.4, @babel/plugin-transform-for-of@npm:^7.12.1, @babel/plugin-transform-for-of@npm:^7.18.6": version: 7.18.8 resolution: "@babel/plugin-transform-for-of@npm:7.18.8" dependencies: @@ -1511,6 +1771,19 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-function-name@npm:^7.10.4": + version: 7.18.9 + resolution: "@babel/plugin-transform-function-name@npm:7.18.9" + dependencies: + "@babel/helper-compilation-targets": ^7.18.9 + "@babel/helper-function-name": ^7.18.9 + "@babel/helper-plugin-utils": ^7.18.9 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 95100707fe00b3e388c059700fbdccf83c2cdf3b7fec8035cdd6c01dd80a1d9efb2821fec1357a62533ebbcbb3f6c361666866a3818486f1172e62f2b692de64 + languageName: node + linkType: hard + "@babel/plugin-transform-instanceof@npm:^7.10.4, @babel/plugin-transform-instanceof@npm:^7.12.1, @babel/plugin-transform-instanceof@npm:^7.12.13, @babel/plugin-transform-instanceof@npm:^7.14.5, @babel/plugin-transform-instanceof@npm:^7.16.0, @babel/plugin-transform-instanceof@npm:^7.8.3": version: 7.18.6 resolution: "@babel/plugin-transform-instanceof@npm:7.18.6" @@ -1533,7 +1806,18 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-member-expression-literals@npm:^7.0.0, @babel/plugin-transform-member-expression-literals@npm:^7.18.6": +"@babel/plugin-transform-literals@npm:^7.10.4": + version: 7.18.9 + resolution: "@babel/plugin-transform-literals@npm:7.18.9" + dependencies: + "@babel/helper-plugin-utils": ^7.18.9 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 7b0d59920dd5a1679a2214dde0d785ce7c0ed75cb6d46b618e7822dcd11fb347be2abb99444019262b6561369b85b95ab96603357773a75126b3d1c4c289b822 + languageName: node + linkType: hard + +"@babel/plugin-transform-member-expression-literals@npm:^7.0.0, @babel/plugin-transform-member-expression-literals@npm:^7.10.4, @babel/plugin-transform-member-expression-literals@npm:^7.18.6": version: 7.18.6 resolution: "@babel/plugin-transform-member-expression-literals@npm:7.18.6" dependencies: @@ -1544,6 +1828,18 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-modules-amd@npm:^7.10.4": + version: 7.20.11 + resolution: "@babel/plugin-transform-modules-amd@npm:7.20.11" + dependencies: + "@babel/helper-module-transforms": ^7.20.11 + "@babel/helper-plugin-utils": ^7.20.2 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 327077cc746d2ef14d0792a970058d9b7170ff480c1d1d7acf874ef7cfeae0c680e86a45896ea27066e9ebdd82dc2be09d321385eef1e0b4255659d75ea2e008 + languageName: node + linkType: hard + "@babel/plugin-transform-modules-amd@npm:^7.18.6": version: 7.18.6 resolution: "@babel/plugin-transform-modules-amd@npm:7.18.6" @@ -1571,6 +1867,33 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-modules-commonjs@npm:^7.10.4": + version: 7.20.11 + resolution: "@babel/plugin-transform-modules-commonjs@npm:7.20.11" + dependencies: + "@babel/helper-module-transforms": ^7.20.11 + "@babel/helper-plugin-utils": ^7.20.2 + "@babel/helper-simple-access": ^7.20.2 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: f3a3281c252a978255076ff7274e4ac1ec252e0db4b3d73122c278ce9fd8318179fc804638ce726870146fa0845e2559711453ce7a391dc2a792d96dc0f6b04c + languageName: node + linkType: hard + +"@babel/plugin-transform-modules-systemjs@npm:^7.10.4": + version: 7.20.11 + resolution: "@babel/plugin-transform-modules-systemjs@npm:7.20.11" + dependencies: + "@babel/helper-hoist-variables": ^7.18.6 + "@babel/helper-module-transforms": ^7.20.11 + "@babel/helper-plugin-utils": ^7.20.2 + "@babel/helper-validator-identifier": ^7.19.1 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 1843b2044b711765581d6130ea7901afde6e6f5af4e4219ab675033a090f4dacb6656bfada8f211a2cd9bbae256c7f4bd0b8613b750e56674feee5252de1ad76 + languageName: node + linkType: hard + "@babel/plugin-transform-modules-systemjs@npm:^7.18.6": version: 7.18.6 resolution: "@babel/plugin-transform-modules-systemjs@npm:7.18.6" @@ -1586,7 +1909,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-modules-umd@npm:^7.18.6": +"@babel/plugin-transform-modules-umd@npm:^7.10.4, @babel/plugin-transform-modules-umd@npm:^7.18.6": version: 7.18.6 resolution: "@babel/plugin-transform-modules-umd@npm:7.18.6" dependencies: @@ -1598,6 +1921,18 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-named-capturing-groups-regex@npm:^7.10.4": + version: 7.20.5 + resolution: "@babel/plugin-transform-named-capturing-groups-regex@npm:7.20.5" + dependencies: + "@babel/helper-create-regexp-features-plugin": ^7.20.5 + "@babel/helper-plugin-utils": ^7.20.2 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 0ca94f716c70f96a0d5e79211ab7e7614efc9aa2940e6009086b16136f2558ae27b7acf9f88bc0a241882ca3192cc66c477fa0eb1cfdda54974ffc2b8846d3e4 + languageName: node + linkType: hard + "@babel/plugin-transform-named-capturing-groups-regex@npm:^7.18.6": version: 7.18.6 resolution: "@babel/plugin-transform-named-capturing-groups-regex@npm:7.18.6" @@ -1610,7 +1945,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-new-target@npm:^7.18.6": +"@babel/plugin-transform-new-target@npm:^7.10.4, @babel/plugin-transform-new-target@npm:^7.18.6": version: 7.18.6 resolution: "@babel/plugin-transform-new-target@npm:7.18.6" dependencies: @@ -1621,7 +1956,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-object-super@npm:^7.0.0, @babel/plugin-transform-object-super@npm:^7.18.6": +"@babel/plugin-transform-object-super@npm:^7.0.0, @babel/plugin-transform-object-super@npm:^7.10.4, @babel/plugin-transform-object-super@npm:^7.18.6": version: 7.18.6 resolution: "@babel/plugin-transform-object-super@npm:7.18.6" dependencies: @@ -1644,7 +1979,18 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-property-literals@npm:^7.0.0, @babel/plugin-transform-property-literals@npm:^7.18.6": +"@babel/plugin-transform-parameters@npm:^7.10.4, @babel/plugin-transform-parameters@npm:^7.20.7": + version: 7.20.7 + resolution: "@babel/plugin-transform-parameters@npm:7.20.7" + dependencies: + "@babel/helper-plugin-utils": ^7.20.2 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: faef20aaebfbbbcd00bffbe75b20c4953852843c0f22eee0177194025e0980fd8c435655a6178ecfdd4f4b3b8677dde41aa6c32394f290b2526519074dbbe33a + languageName: node + linkType: hard + +"@babel/plugin-transform-property-literals@npm:^7.0.0, @babel/plugin-transform-property-literals@npm:^7.10.4, @babel/plugin-transform-property-literals@npm:^7.18.6": version: 7.18.6 resolution: "@babel/plugin-transform-property-literals@npm:7.18.6" dependencies: @@ -1704,6 +2050,18 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-regenerator@npm:^7.10.4": + version: 7.20.5 + resolution: "@babel/plugin-transform-regenerator@npm:7.20.5" + dependencies: + "@babel/helper-plugin-utils": ^7.20.2 + regenerator-transform: ^0.15.1 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 4f390ec2687d34d11a8154244d246704be19eeb2ac50b38730ba02ee9adde8a4a4110c79cab0d0778ab3e023034b26fe8745752a9a7624d613e2267b86906b64 + languageName: node + linkType: hard + "@babel/plugin-transform-regenerator@npm:^7.12.1, @babel/plugin-transform-regenerator@npm:^7.18.6": version: 7.18.6 resolution: "@babel/plugin-transform-regenerator@npm:7.18.6" @@ -1716,7 +2074,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-reserved-words@npm:^7.18.6": +"@babel/plugin-transform-reserved-words@npm:^7.10.4, @babel/plugin-transform-reserved-words@npm:^7.18.6": version: 7.18.6 resolution: "@babel/plugin-transform-reserved-words@npm:7.18.6" dependencies: @@ -1743,7 +2101,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-shorthand-properties@npm:^7.0.0, @babel/plugin-transform-shorthand-properties@npm:^7.12.1, @babel/plugin-transform-shorthand-properties@npm:^7.18.6": +"@babel/plugin-transform-shorthand-properties@npm:^7.0.0, @babel/plugin-transform-shorthand-properties@npm:^7.10.4, @babel/plugin-transform-shorthand-properties@npm:^7.12.1, @babel/plugin-transform-shorthand-properties@npm:^7.18.6": version: 7.18.6 resolution: "@babel/plugin-transform-shorthand-properties@npm:7.18.6" dependencies: @@ -1766,7 +2124,19 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-sticky-regex@npm:^7.18.6": +"@babel/plugin-transform-spread@npm:^7.11.0": + version: 7.20.7 + resolution: "@babel/plugin-transform-spread@npm:7.20.7" + dependencies: + "@babel/helper-plugin-utils": ^7.20.2 + "@babel/helper-skip-transparent-expression-wrappers": ^7.20.0 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 6abd206942e1fd322791707e7e15aa823f9829d8965facbed4abb0f85d51355d0bb21ac8d7184dea22de3bb5853e807ae6b5b74c621507b912c345cbce4a37b4 + languageName: node + linkType: hard + +"@babel/plugin-transform-sticky-regex@npm:^7.10.4, @babel/plugin-transform-sticky-regex@npm:^7.18.6": version: 7.18.6 resolution: "@babel/plugin-transform-sticky-regex@npm:7.18.6" dependencies: @@ -1788,6 +2158,28 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-template-literals@npm:^7.10.4": + version: 7.18.9 + resolution: "@babel/plugin-transform-template-literals@npm:7.18.9" + dependencies: + "@babel/helper-plugin-utils": ^7.18.9 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: d1a5e55ed8c3b1186fbba2a7b3e9d880cb3987b846376f51a73216a8894b9c9d6f6c6e2d3cadb17d76f2477552db5383d817169d5b92fcf08ee0fa5b88213c15 + languageName: node + linkType: hard + +"@babel/plugin-transform-typeof-symbol@npm:^7.10.4": + version: 7.18.9 + resolution: "@babel/plugin-transform-typeof-symbol@npm:7.18.9" + dependencies: + "@babel/helper-plugin-utils": ^7.18.9 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: c42e00635aa9d1c597d339c9023e0f9bfa3cd7af55c00cb8a6461036102b0facdcd3059456d4fee0a63675aeecca62fc84ee01f28b23139c6ae03e6d61c86906 + languageName: node + linkType: hard + "@babel/plugin-transform-typeof-symbol@npm:^7.18.6": version: 7.18.6 resolution: "@babel/plugin-transform-typeof-symbol@npm:7.18.6" @@ -1812,6 +2204,17 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-unicode-escapes@npm:^7.10.4": + version: 7.18.10 + resolution: "@babel/plugin-transform-unicode-escapes@npm:7.18.10" + dependencies: + "@babel/helper-plugin-utils": ^7.18.9 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 1587c3497549a4ad1b75d5b63f1d6ced839d4078dc7df3b5df362c8669f3e9cbed975d5c55632bf8c574847d8df03553851e1b85d1e81a568fdfd2466fcd4198 + languageName: node + linkType: hard + "@babel/plugin-transform-unicode-escapes@npm:^7.18.6": version: 7.18.6 resolution: "@babel/plugin-transform-unicode-escapes@npm:7.18.6" @@ -1823,7 +2226,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-unicode-regex@npm:^7.18.6": +"@babel/plugin-transform-unicode-regex@npm:^7.10.4, @babel/plugin-transform-unicode-regex@npm:^7.18.6": version: 7.18.6 resolution: "@babel/plugin-transform-unicode-regex@npm:7.18.6" dependencies: @@ -1845,6 +2248,84 @@ __metadata: languageName: node linkType: hard +"@babel/preset-env@npm:7.11.5": + version: 7.11.5 + resolution: "@babel/preset-env@npm:7.11.5" + dependencies: + "@babel/compat-data": ^7.11.0 + "@babel/helper-compilation-targets": ^7.10.4 + "@babel/helper-module-imports": ^7.10.4 + "@babel/helper-plugin-utils": ^7.10.4 + "@babel/plugin-proposal-async-generator-functions": ^7.10.4 + "@babel/plugin-proposal-class-properties": ^7.10.4 + "@babel/plugin-proposal-dynamic-import": ^7.10.4 + "@babel/plugin-proposal-export-namespace-from": ^7.10.4 + "@babel/plugin-proposal-json-strings": ^7.10.4 + "@babel/plugin-proposal-logical-assignment-operators": ^7.11.0 + "@babel/plugin-proposal-nullish-coalescing-operator": ^7.10.4 + "@babel/plugin-proposal-numeric-separator": ^7.10.4 + "@babel/plugin-proposal-object-rest-spread": ^7.11.0 + "@babel/plugin-proposal-optional-catch-binding": ^7.10.4 + "@babel/plugin-proposal-optional-chaining": ^7.11.0 + "@babel/plugin-proposal-private-methods": ^7.10.4 + "@babel/plugin-proposal-unicode-property-regex": ^7.10.4 + "@babel/plugin-syntax-async-generators": ^7.8.0 + "@babel/plugin-syntax-class-properties": ^7.10.4 + "@babel/plugin-syntax-dynamic-import": ^7.8.0 + "@babel/plugin-syntax-export-namespace-from": ^7.8.3 + "@babel/plugin-syntax-json-strings": ^7.8.0 + "@babel/plugin-syntax-logical-assignment-operators": ^7.10.4 + "@babel/plugin-syntax-nullish-coalescing-operator": ^7.8.0 + "@babel/plugin-syntax-numeric-separator": ^7.10.4 + "@babel/plugin-syntax-object-rest-spread": ^7.8.0 + "@babel/plugin-syntax-optional-catch-binding": ^7.8.0 + "@babel/plugin-syntax-optional-chaining": ^7.8.0 + "@babel/plugin-syntax-top-level-await": ^7.10.4 + "@babel/plugin-transform-arrow-functions": ^7.10.4 + "@babel/plugin-transform-async-to-generator": ^7.10.4 + "@babel/plugin-transform-block-scoped-functions": ^7.10.4 + "@babel/plugin-transform-block-scoping": ^7.10.4 + "@babel/plugin-transform-classes": ^7.10.4 + "@babel/plugin-transform-computed-properties": ^7.10.4 + "@babel/plugin-transform-destructuring": ^7.10.4 + "@babel/plugin-transform-dotall-regex": ^7.10.4 + "@babel/plugin-transform-duplicate-keys": ^7.10.4 + "@babel/plugin-transform-exponentiation-operator": ^7.10.4 + "@babel/plugin-transform-for-of": ^7.10.4 + "@babel/plugin-transform-function-name": ^7.10.4 + "@babel/plugin-transform-literals": ^7.10.4 + "@babel/plugin-transform-member-expression-literals": ^7.10.4 + "@babel/plugin-transform-modules-amd": ^7.10.4 + "@babel/plugin-transform-modules-commonjs": ^7.10.4 + "@babel/plugin-transform-modules-systemjs": ^7.10.4 + "@babel/plugin-transform-modules-umd": ^7.10.4 + "@babel/plugin-transform-named-capturing-groups-regex": ^7.10.4 + "@babel/plugin-transform-new-target": ^7.10.4 + "@babel/plugin-transform-object-super": ^7.10.4 + "@babel/plugin-transform-parameters": ^7.10.4 + "@babel/plugin-transform-property-literals": ^7.10.4 + "@babel/plugin-transform-regenerator": ^7.10.4 + "@babel/plugin-transform-reserved-words": ^7.10.4 + "@babel/plugin-transform-shorthand-properties": ^7.10.4 + "@babel/plugin-transform-spread": ^7.11.0 + "@babel/plugin-transform-sticky-regex": ^7.10.4 + "@babel/plugin-transform-template-literals": ^7.10.4 + "@babel/plugin-transform-typeof-symbol": ^7.10.4 + "@babel/plugin-transform-unicode-escapes": ^7.10.4 + "@babel/plugin-transform-unicode-regex": ^7.10.4 + "@babel/preset-modules": ^0.1.3 + "@babel/types": ^7.11.5 + browserslist: ^4.12.0 + core-js-compat: ^3.6.2 + invariant: ^2.2.2 + levenary: ^1.1.1 + semver: ^5.5.0 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: bfff5f36df8e14df3afdb09af141a6b6283fe38f122f58c46a356dfbb4f15a86c7e6b01f9615e72fa95b13b4ccef1fc1a8419657ffd5bb73e06b9ec2617326bf + languageName: node + linkType: hard + "@babel/preset-env@npm:^7.11.5, @babel/preset-env@npm:^7.12.11, @babel/preset-env@npm:^7.12.7, @babel/preset-env@npm:^7.15.4, @babel/preset-env@npm:^7.15.6, @babel/preset-env@npm:^7.16.4, @babel/preset-env@npm:^7.7.5": version: 7.18.6 resolution: "@babel/preset-env@npm:7.18.6" @@ -1943,7 +2424,7 @@ __metadata: languageName: node linkType: hard -"@babel/preset-modules@npm:^0.1.5": +"@babel/preset-modules@npm:^0.1.3, @babel/preset-modules@npm:^0.1.5": version: 0.1.5 resolution: "@babel/preset-modules@npm:0.1.5" dependencies: @@ -1974,7 +2455,7 @@ __metadata: languageName: node linkType: hard -"@babel/preset-typescript@npm:^7.12.7, @babel/preset-typescript@npm:^7.13.0, @babel/preset-typescript@npm:^7.15.0, @babel/preset-typescript@npm:^7.16.0, @babel/preset-typescript@npm:^7.16.7": +"@babel/preset-typescript@npm:7.18.6, @babel/preset-typescript@npm:^7.12.7, @babel/preset-typescript@npm:^7.13.0, @babel/preset-typescript@npm:^7.15.0, @babel/preset-typescript@npm:^7.16.0, @babel/preset-typescript@npm:^7.16.7": version: 7.18.6 resolution: "@babel/preset-typescript@npm:7.18.6" dependencies: @@ -2017,6 +2498,13 @@ __metadata: languageName: node linkType: hard +"@babel/regjsgen@npm:^0.8.0": + version: 0.8.0 + resolution: "@babel/regjsgen@npm:0.8.0" + checksum: 4f3ddd8c7c96d447e05c8304c1d5ba3a83fcabd8a716bc1091c2f31595cdd43a3a055fff7cb5d3042b8cb7d402d78820fcb4e05d896c605a7d8bcf30f2424c4a + languageName: node + linkType: hard + "@babel/runtime-corejs3@npm:^7.10.2": version: 7.18.6 resolution: "@babel/runtime-corejs3@npm:7.18.6" @@ -2052,6 +2540,17 @@ __metadata: languageName: node linkType: hard +"@babel/template@npm:^7.12.13, @babel/template@npm:^7.18.10, @babel/template@npm:^7.20.7": + version: 7.20.7 + resolution: "@babel/template@npm:7.20.7" + dependencies: + "@babel/code-frame": ^7.18.6 + "@babel/parser": ^7.20.7 + "@babel/types": ^7.20.7 + checksum: 1c6dcf9ac92769e6ab5e3d9048975537d26ab00b869646462ab4583d45e419c01db5144715ec0d70548835a3098c5d5416148c4a0b996a95e8e0b9dc8d042dd3 + languageName: node + linkType: hard + "@babel/template@npm:^7.12.7, @babel/template@npm:^7.16.7, @babel/template@npm:^7.18.6, @babel/template@npm:^7.3.3": version: 7.18.6 resolution: "@babel/template@npm:7.18.6" @@ -2063,17 +2562,6 @@ __metadata: languageName: node linkType: hard -"@babel/template@npm:^7.18.10, @babel/template@npm:^7.20.7": - version: 7.20.7 - resolution: "@babel/template@npm:7.20.7" - dependencies: - "@babel/code-frame": ^7.18.6 - "@babel/parser": ^7.20.7 - "@babel/types": ^7.20.7 - checksum: 1c6dcf9ac92769e6ab5e3d9048975537d26ab00b869646462ab4583d45e419c01db5144715ec0d70548835a3098c5d5416148c4a0b996a95e8e0b9dc8d042dd3 - languageName: node - linkType: hard - "@babel/traverse@npm:^7.1.0, @babel/traverse@npm:^7.1.6, @babel/traverse@npm:^7.12.11, @babel/traverse@npm:^7.12.9, @babel/traverse@npm:^7.13.0, @babel/traverse@npm:^7.14.0, @babel/traverse@npm:^7.15.4, @babel/traverse@npm:^7.16.8, @babel/traverse@npm:^7.18.6, @babel/traverse@npm:^7.18.8, @babel/traverse@npm:^7.7.2": version: 7.18.8 resolution: "@babel/traverse@npm:7.18.8" @@ -2092,7 +2580,7 @@ __metadata: languageName: node linkType: hard -"@babel/traverse@npm:^7.20.10, @babel/traverse@npm:^7.20.12, @babel/traverse@npm:^7.20.13, @babel/traverse@npm:^7.4.5": +"@babel/traverse@npm:^7.14.2, @babel/traverse@npm:^7.20.10, @babel/traverse@npm:^7.20.12, @babel/traverse@npm:^7.20.13, @babel/traverse@npm:^7.20.5, @babel/traverse@npm:^7.4.5": version: 7.20.13 resolution: "@babel/traverse@npm:7.20.13" dependencies: @@ -2120,7 +2608,7 @@ __metadata: languageName: node linkType: hard -"@babel/types@npm:^7.19.0, @babel/types@npm:^7.20.2, @babel/types@npm:^7.20.7": +"@babel/types@npm:^7.11.5, @babel/types@npm:^7.14.2, @babel/types@npm:^7.18.9, @babel/types@npm:^7.19.0, @babel/types@npm:^7.20.0, @babel/types@npm:^7.20.2, @babel/types@npm:^7.20.5, @babel/types@npm:^7.20.7": version: 7.20.7 resolution: "@babel/types@npm:7.20.7" dependencies: @@ -4525,7 +5013,7 @@ __metadata: languageName: node linkType: hard -"@jridgewell/sourcemap-codec@npm:1.4.14, @jridgewell/sourcemap-codec@npm:^1.4.10": +"@jridgewell/sourcemap-codec@npm:1.4.14, @jridgewell/sourcemap-codec@npm:^1.4.10, @jridgewell/sourcemap-codec@npm:^1.4.13": version: 1.4.14 resolution: "@jridgewell/sourcemap-codec@npm:1.4.14" checksum: 3fbaff1387c1338b097eeb6ff92890d7838f7de0dde259e4983763b44540bfd5ca6a1f7644dc8ad003a57f7e80670d5b96a8402f1386ba9aee074743ae9bad51 @@ -4812,8 +5300,11 @@ __metadata: resolution: "@medusajs/medusa-oas-cli@workspace:packages/oas/medusa-oas-cli" dependencies: "@medusajs/medusa": "*" + "@medusajs/openapi-typescript-codegen": "*" "@readme/openapi-parser": ^2.4.0 + "@types/lodash": ^4.14.191 commander: ^10.0.0 + lodash: ^4.17.21 openapi3-ts: ^3.1.2 swagger-inline: ^6.1.0 ts-node: ^10.9.1 @@ -4894,6 +5385,43 @@ __metadata: languageName: unknown linkType: soft +"@medusajs/openapi-typescript-codegen@*, @medusajs/openapi-typescript-codegen@workspace:packages/oas/openapi-typescript-codegen": + version: 0.0.0-use.local + resolution: "@medusajs/openapi-typescript-codegen@workspace:packages/oas/openapi-typescript-codegen" + dependencies: + "@babel/cli": 7.14.3 + "@babel/core": 7.14.3 + "@babel/preset-env": 7.11.5 + "@babel/preset-typescript": 7.18.6 + "@rollup/plugin-commonjs": 24.0.0 + "@rollup/plugin-node-resolve": 15.0.1 + "@rollup/plugin-typescript": 9.0.2 + "@types/fs-extra": ^9.0.12 + "@types/jest": 27.5.0 + "@types/node": 18.11.9 + "@types/node-fetch": 2.6.2 + "@types/pascalcase": ^1.0.1 + "@types/qs": 6.9.7 + abort-controller: 3.0.0 + axios: 1.2.0 + camelcase: ^6.3.0 + commander: ^9.4.1 + form-data: 4.0.0 + fs-extra: ^10.1.0 + handlebars: ^4.7.7 + jest: 26.6.3 + jest-cli: 26.6.3 + json-schema-ref-parser: ^9.0.9 + node-fetch: 2.6.7 + pascalcase: ^2.0.0 + qs: 6.10.3 + rollup: 3.9.1 + rollup-plugin-terser: 7.0.2 + tslib: 2.3.1 + typescript: 4.9.5 + languageName: unknown + linkType: soft + "@medusajs/stock-location@workspace:packages/stock-location": version: 0.0.0-use.local resolution: "@medusajs/stock-location@workspace:packages/stock-location" @@ -5004,6 +5532,25 @@ __metadata: languageName: node linkType: hard +"@nicolo-ribaudo/chokidar-2@npm:2.1.8-no-fsevents": + version: 2.1.8-no-fsevents + resolution: "@nicolo-ribaudo/chokidar-2@npm:2.1.8-no-fsevents" + dependencies: + anymatch: ^2.0.0 + async-each: ^1.0.1 + braces: ^2.3.2 + glob-parent: ^3.1.0 + inherits: ^2.0.3 + is-binary-path: ^1.0.0 + is-glob: ^4.0.0 + normalize-path: ^3.0.0 + path-is-absolute: ^1.0.0 + readdirp: ^2.2.1 + upath: ^1.1.1 + checksum: 8ca958d7763d73a7fc63b208b865bf656d5970c7161014c71ef190aa0e30e14766f286ecb6c856f7fa2e2b5da7252788baab1382e96ed2a07f5c3d65131947f9 + languageName: node + linkType: hard + "@nicolo-ribaudo/chokidar-2@npm:2.1.8-no-fsevents.3": version: 2.1.8-no-fsevents.3 resolution: "@nicolo-ribaudo/chokidar-2@npm:2.1.8-no-fsevents.3" @@ -5959,6 +6506,25 @@ __metadata: languageName: node linkType: hard +"@rollup/plugin-commonjs@npm:24.0.0": + version: 24.0.0 + resolution: "@rollup/plugin-commonjs@npm:24.0.0" + dependencies: + "@rollup/pluginutils": ^5.0.1 + commondir: ^1.0.1 + estree-walker: ^2.0.2 + glob: ^8.0.3 + is-reference: 1.2.1 + magic-string: ^0.27.0 + peerDependencies: + rollup: ^2.68.0||^3.0.0 + peerDependenciesMeta: + rollup: + optional: true + checksum: ae2ed1d49722fad55f7ef7629baad5fb31bc389e6042912fb5cb9623c2c9c0def27c66bbd1aea72f1fcd65caa1410a9de80a0377f85d8cd8068f97c0adeaf3da + languageName: node + linkType: hard + "@rollup/plugin-commonjs@npm:^17.0.0": version: 17.1.0 resolution: "@rollup/plugin-commonjs@npm:17.1.0" @@ -5987,6 +6553,25 @@ __metadata: languageName: node linkType: hard +"@rollup/plugin-node-resolve@npm:15.0.1": + version: 15.0.1 + resolution: "@rollup/plugin-node-resolve@npm:15.0.1" + dependencies: + "@rollup/pluginutils": ^5.0.1 + "@types/resolve": 1.20.2 + deepmerge: ^4.2.2 + is-builtin-module: ^3.2.0 + is-module: ^1.0.0 + resolve: ^1.22.1 + peerDependencies: + rollup: ^2.78.0||^3.0.0 + peerDependenciesMeta: + rollup: + optional: true + checksum: 82af7876281e9459ea86cedf571816b61883055f98d3ecd9306e869674b344b5e38d57d589f409d255fe22021dd81f4a65702e9e8c859eded45bebccb8644e1b + languageName: node + linkType: hard + "@rollup/plugin-node-resolve@npm:^11.0.1": version: 11.2.1 resolution: "@rollup/plugin-node-resolve@npm:11.2.1" @@ -6003,6 +6588,25 @@ __metadata: languageName: node linkType: hard +"@rollup/plugin-typescript@npm:9.0.2": + version: 9.0.2 + resolution: "@rollup/plugin-typescript@npm:9.0.2" + dependencies: + "@rollup/pluginutils": ^5.0.1 + resolve: ^1.22.1 + peerDependencies: + rollup: ^2.14.0||^3.0.0 + tslib: "*" + typescript: ">=3.7.0" + peerDependenciesMeta: + rollup: + optional: true + tslib: + optional: true + checksum: 799387ede61c1bd34f6822e9f65dffd465eaa7ed9b3d8797f61497d8ee693bca10c7ec8b664d9f7e47fdf70b9304af12f45a27a8d3f39546001e17f31cf69d93 + languageName: node + linkType: hard + "@rollup/pluginutils@npm:^3.0.8, @rollup/pluginutils@npm:^3.1.0": version: 3.1.0 resolution: "@rollup/pluginutils@npm:3.1.0" @@ -6016,6 +6620,22 @@ __metadata: languageName: node linkType: hard +"@rollup/pluginutils@npm:^5.0.1": + version: 5.0.2 + resolution: "@rollup/pluginutils@npm:5.0.2" + dependencies: + "@types/estree": ^1.0.0 + estree-walker: ^2.0.2 + picomatch: ^2.3.1 + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0 + peerDependenciesMeta: + rollup: + optional: true + checksum: b06f73c15bb59418aa6fbfead5675bab2d6922e15663525ffc2eb8429530bc5add516600adb251cfbf9b60f3d12fb821cde155cb5103415154a476bd0f163432 + languageName: node + linkType: hard + "@segment/loosely-validate-event@npm:^2.0.0": version: 2.0.0 resolution: "@segment/loosely-validate-event@npm:2.0.0" @@ -7897,6 +8517,13 @@ __metadata: languageName: node linkType: hard +"@types/estree@npm:^1.0.0": + version: 1.0.0 + resolution: "@types/estree@npm:1.0.0" + checksum: 4e73ff606bf7c7ccdaa66092de650c410a4ad2ecc388fdbed8242cac9dbcad72407e1ceff041b7da691babb02ff74ab885d6231fb09368fdd1eabbf1b5253d49 + languageName: node + linkType: hard + "@types/express-serve-static-core@npm:^4.17.18": version: 4.17.29 resolution: "@types/express-serve-static-core@npm:4.17.29" @@ -7943,7 +8570,7 @@ __metadata: languageName: node linkType: hard -"@types/fs-extra@npm:^9.0.13": +"@types/fs-extra@npm:^9.0.12, @types/fs-extra@npm:^9.0.13": version: 9.0.13 resolution: "@types/fs-extra@npm:9.0.13" dependencies: @@ -8099,6 +8726,16 @@ __metadata: languageName: node linkType: hard +"@types/jest@npm:27.5.0": + version: 27.5.0 + resolution: "@types/jest@npm:27.5.0" + dependencies: + jest-matcher-utils: ^27.0.0 + pretty-format: ^27.0.0 + checksum: bb26b293210827c1d65efde9bf72feb0d2b7c2816911e086fd9a9fb7690cfa9416e10f4ddcfbc8e5017442a682da6273781b8b0f78951e0bb7e40993f6a52e7b + languageName: node + linkType: hard + "@types/jest@npm:^27.0.1, @types/jest@npm:^27.0.3, @types/jest@npm:^27.5.2": version: 27.5.2 resolution: "@types/jest@npm:27.5.2" @@ -8237,7 +8874,7 @@ __metadata: languageName: node linkType: hard -"@types/node-fetch@npm:2, @types/node-fetch@npm:^2.5.7": +"@types/node-fetch@npm:2, @types/node-fetch@npm:2.6.2, @types/node-fetch@npm:^2.5.7": version: 2.6.2 resolution: "@types/node-fetch@npm:2.6.2" dependencies: @@ -8268,6 +8905,13 @@ __metadata: languageName: node linkType: hard +"@types/node@npm:18.11.9": + version: 18.11.9 + resolution: "@types/node@npm:18.11.9" + checksum: aeaa925406f841c41679b32def9391a9892171e977105e025050e9f66e2830b4c50d0d974a1af0077ead3337a1f3bdf49ee7e7f402ebf2e034a3f97d9d240dba + languageName: node + linkType: hard + "@types/node@npm:^12.7.1": version: 12.20.55 resolution: "@types/node@npm:12.20.55" @@ -8331,6 +8975,13 @@ __metadata: languageName: node linkType: hard +"@types/pascalcase@npm:^1.0.1": + version: 1.0.1 + resolution: "@types/pascalcase@npm:1.0.1" + checksum: beb5e1568f9dc2335c20c1f7df1332b0a357c6492e3ba04e14e0b908ff2e10509c94a991d1f90a384a0dae7c862d8c1107141dacab80532738fadd085cdbb26e + languageName: node + linkType: hard + "@types/prettier@npm:^1.19.0": version: 1.19.1 resolution: "@types/prettier@npm:1.19.1" @@ -8359,7 +9010,7 @@ __metadata: languageName: node linkType: hard -"@types/qs@npm:*, @types/qs@npm:^6.9.5": +"@types/qs@npm:*, @types/qs@npm:6.9.7, @types/qs@npm:^6.9.5": version: 6.9.7 resolution: "@types/qs@npm:6.9.7" checksum: 157eb05f4c75790b0ebdcf7b0547ff117feabc8cda03c3cac3d3ea82bb19a1912e76a411df3eb0bdd01026a9770f07bc0e7e3fbe39ebb31c1be4564c16be35f1 @@ -8458,6 +9109,13 @@ __metadata: languageName: node linkType: hard +"@types/resolve@npm:1.20.2": + version: 1.20.2 + resolution: "@types/resolve@npm:1.20.2" + checksum: c5b7e1770feb5ccfb6802f6ad82a7b0d50874c99331e0c9b259e415e55a38d7a86ad0901c57665d93f75938be2a6a0bc9aa06c9749192cadb2e4512800bbc6e6 + languageName: node + linkType: hard + "@types/responselike@npm:*, @types/responselike@npm:^1.0.0": version: 1.0.0 resolution: "@types/responselike@npm:1.0.0" @@ -10502,6 +11160,17 @@ __metadata: languageName: node linkType: hard +"axios@npm:1.2.0": + version: 1.2.0 + resolution: "axios@npm:1.2.0" + dependencies: + follow-redirects: ^1.15.0 + form-data: ^4.0.0 + proxy-from-env: ^1.1.0 + checksum: 2a9a73ca55010cab987b612b358a0bf128ebc572c5d63cdb66fb9c336a5718752749f2ba8b38b9745a6e33edb83ffec2dd65c5a7118ce94e5cae1a9ea6fc74c2 + languageName: node + linkType: hard + "axios@npm:^0.19.0, axios@npm:^0.19.2": version: 0.19.2 resolution: "axios@npm:0.19.2" @@ -11593,7 +12262,7 @@ __metadata: languageName: node linkType: hard -"browserslist@npm:^4.21.3": +"browserslist@npm:^4.21.3, browserslist@npm:^4.21.4": version: 4.21.5 resolution: "browserslist@npm:4.21.5" dependencies: @@ -11691,7 +12360,7 @@ __metadata: languageName: node linkType: hard -"builtin-modules@npm:^3.1.0": +"builtin-modules@npm:^3.1.0, builtin-modules@npm:^3.3.0": version: 3.3.0 resolution: "builtin-modules@npm:3.3.0" checksum: 2cb3448b4f7306dc853632a4fcddc95e8d4e4b9868c139400027b71938fc6806d4ff44007deffb362ac85724bd40c2c6452fb6a0aa4531650eeddb98d8e5ee8a @@ -12033,7 +12702,7 @@ __metadata: languageName: node linkType: hard -"camelcase@npm:^6.0.0, camelcase@npm:^6.2.0": +"camelcase@npm:^6.0.0, camelcase@npm:^6.2.0, camelcase@npm:^6.2.1, camelcase@npm:^6.3.0": version: 6.3.0 resolution: "camelcase@npm:6.3.0" checksum: 0d701658219bd3116d12da3eab31acddb3f9440790c0792e0d398f0a520a6a4058018e546862b6fba89d7ae990efaeb97da71e1913e9ebf5a8b5621a3d55c710 @@ -12869,6 +13538,13 @@ __metadata: languageName: node linkType: hard +"commander@npm:^9.4.1": + version: 9.5.0 + resolution: "commander@npm:9.5.0" + checksum: 5f7784fbda2aaec39e89eb46f06a999e00224b3763dc65976e05929ec486e174fe9aac2655f03ba6a5e83875bd173be5283dc19309b7c65954701c02025b3c1d + languageName: node + linkType: hard + "comment-patterns@npm:^0.12.0": version: 0.12.2 resolution: "comment-patterns@npm:0.12.2" @@ -13234,6 +13910,15 @@ __metadata: languageName: node linkType: hard +"core-js-compat@npm:^3.6.2": + version: 3.27.2 + resolution: "core-js-compat@npm:3.27.2" + dependencies: + browserslist: ^4.21.4 + checksum: 1bb3b63ae46420b55d78171750f58f73f1a0df8312d3be37111588f068f06a2c18f397737f5603c80fa78bd05f224c93fb60459ff05d80f7dbeeb8a97fbdf457 + languageName: node + linkType: hard + "core-js-pure@npm:^3.20.2, core-js-pure@npm:^3.8.1": version: 3.23.4 resolution: "core-js-pure@npm:3.23.4" @@ -16167,7 +16852,7 @@ __metadata: languageName: node linkType: hard -"estree-walker@npm:^2.0.1": +"estree-walker@npm:^2.0.1, estree-walker@npm:^2.0.2": version: 2.0.2 resolution: "estree-walker@npm:2.0.2" checksum: 53a6c54e2019b8c914dc395890153ffdc2322781acf4bd7d1a32d7aedc1710807bdcd866ac133903d5629ec601fbb50abe8c2e5553c7f5a0afdd9b6af6c945af @@ -17241,6 +17926,16 @@ __metadata: languageName: node linkType: hard +"follow-redirects@npm:^1.15.0": + version: 1.15.2 + resolution: "follow-redirects@npm:1.15.2" + peerDependenciesMeta: + debug: + optional: true + checksum: da5932b70e63944d38eecaa16954bac4347036f08303c913d166eda74809d8797d38386e3a0eb1d2fe37d2aaff2764cce8e9dbd99459d860cf2cdfa237923b5f + languageName: node + linkType: hard + "for-each@npm:^0.3.3": version: 0.3.3 resolution: "for-each@npm:0.3.3" @@ -18455,6 +19150,19 @@ __metadata: languageName: node linkType: hard +"glob@npm:^8.0.3": + version: 8.1.0 + resolution: "glob@npm:8.1.0" + dependencies: + fs.realpath: ^1.0.0 + inflight: ^1.0.4 + inherits: 2 + minimatch: ^5.0.1 + once: ^1.3.0 + checksum: cb0b5cab17a59c57299376abe5646c7070f8acb89df5595b492dba3bfb43d301a46c01e5695f01154e6553168207cb60d4eaf07d3be4bc3eb9b0457c5c561d0f + languageName: node + linkType: hard + "global-dirs@npm:^3.0.0": version: 3.0.0 resolution: "global-dirs@npm:3.0.0" @@ -19873,7 +20581,7 @@ __metadata: languageName: node linkType: hard -"invariant@npm:^2.2.3, invariant@npm:^2.2.4": +"invariant@npm:^2.2.2, invariant@npm:^2.2.3, invariant@npm:^2.2.4": version: 2.2.4 resolution: "invariant@npm:2.2.4" dependencies: @@ -20071,6 +20779,15 @@ __metadata: languageName: node linkType: hard +"is-builtin-module@npm:^3.2.0": + version: 3.2.1 + resolution: "is-builtin-module@npm:3.2.1" + dependencies: + builtin-modules: ^3.3.0 + checksum: 5a66937a03f3b18803381518f0ef679752ac18cdb7dd53b5e23ee8df8d440558737bd8dcc04d2aae555909d2ecb4a81b5c0d334d119402584b61e6a003e31af1 + languageName: node + linkType: hard + "is-callable@npm:^1.1.3, is-callable@npm:^1.1.4, is-callable@npm:^1.2.4": version: 1.2.4 resolution: "is-callable@npm:1.2.4" @@ -20504,7 +21221,7 @@ __metadata: languageName: node linkType: hard -"is-reference@npm:^1.2.1": +"is-reference@npm:1.2.1, is-reference@npm:^1.2.1": version: 1.2.1 resolution: "is-reference@npm:1.2.1" dependencies: @@ -21031,6 +21748,29 @@ __metadata: languageName: node linkType: hard +"jest-cli@npm:26.6.3, jest-cli@npm:^26.6.3": + version: 26.6.3 + resolution: "jest-cli@npm:26.6.3" + dependencies: + "@jest/core": ^26.6.3 + "@jest/test-result": ^26.6.2 + "@jest/types": ^26.6.2 + chalk: ^4.0.0 + exit: ^0.1.2 + graceful-fs: ^4.2.4 + import-local: ^3.0.2 + is-ci: ^2.0.0 + jest-config: ^26.6.3 + jest-util: ^26.6.2 + jest-validate: ^26.6.2 + prompts: ^2.0.1 + yargs: ^15.4.1 + bin: + jest: bin/jest.js + checksum: 3f62c26b300549115bcfc0393d7d49467d414d200bb211a8843fd48d0296ddbfc5e6fe808c64ad2039127657b662e3ba3db44166341bd5db2d089bf09cf82a2c + languageName: node + linkType: hard + "jest-cli@npm:^25.5.4": version: 25.5.4 resolution: "jest-cli@npm:25.5.4" @@ -21055,29 +21795,6 @@ __metadata: languageName: node linkType: hard -"jest-cli@npm:^26.6.3": - version: 26.6.3 - resolution: "jest-cli@npm:26.6.3" - dependencies: - "@jest/core": ^26.6.3 - "@jest/test-result": ^26.6.2 - "@jest/types": ^26.6.2 - chalk: ^4.0.0 - exit: ^0.1.2 - graceful-fs: ^4.2.4 - import-local: ^3.0.2 - is-ci: ^2.0.0 - jest-config: ^26.6.3 - jest-util: ^26.6.2 - jest-validate: ^26.6.2 - prompts: ^2.0.1 - yargs: ^15.4.1 - bin: - jest: bin/jest.js - checksum: 3f62c26b300549115bcfc0393d7d49467d414d200bb211a8843fd48d0296ddbfc5e6fe808c64ad2039127657b662e3ba3db44166341bd5db2d089bf09cf82a2c - languageName: node - linkType: hard - "jest-cli@npm:^27.5.1": version: 27.5.1 resolution: "jest-cli@npm:27.5.1" @@ -22668,6 +23385,19 @@ __metadata: languageName: node linkType: hard +"jest@npm:26.6.3, jest@npm:^26.6.3": + version: 26.6.3 + resolution: "jest@npm:26.6.3" + dependencies: + "@jest/core": ^26.6.3 + import-local: ^3.0.2 + jest-cli: ^26.6.3 + bin: + jest: bin/jest.js + checksum: 4469f5c426f5b00855e2264dc4fce5ab16c0fab31d2dc6fc829d769ca7ec84a9c74763f7c1d281d085ad55897927a08df2b4778b0df899a66188ff0722e17d29 + languageName: node + linkType: hard + "jest@npm:^25.5.4": version: 25.5.4 resolution: "jest@npm:25.5.4" @@ -22681,19 +23411,6 @@ __metadata: languageName: node linkType: hard -"jest@npm:^26.6.3": - version: 26.6.3 - resolution: "jest@npm:26.6.3" - dependencies: - "@jest/core": ^26.6.3 - import-local: ^3.0.2 - jest-cli: ^26.6.3 - bin: - jest: bin/jest.js - checksum: 4469f5c426f5b00855e2264dc4fce5ab16c0fab31d2dc6fc829d769ca7ec84a9c74763f7c1d281d085ad55897927a08df2b4778b0df899a66188ff0722e17d29 - languageName: node - linkType: hard - "jest@npm:^27.0.6, jest@npm:^27.4.7": version: 27.5.1 resolution: "jest@npm:27.5.1" @@ -23023,6 +23740,15 @@ __metadata: languageName: node linkType: hard +"json-schema-ref-parser@npm:^9.0.9": + version: 9.0.9 + resolution: "json-schema-ref-parser@npm:9.0.9" + dependencies: + "@apidevtools/json-schema-ref-parser": 9.0.9 + checksum: c6602a4e4f003629819a83540520850f3a06cb49a738d273854e4e2d8ebabb51b80cfc17d66cfc00ccadcb3a4c33eedf85b26b0af3b2c142f68f9f9dd5531a9c + languageName: node + linkType: hard + "json-schema-traverse@npm:^0.4.1": version: 0.4.1 resolution: "json-schema-traverse@npm:0.4.1" @@ -23475,6 +24201,15 @@ __metadata: languageName: node linkType: hard +"levenary@npm:^1.1.1": + version: 1.1.1 + resolution: "levenary@npm:1.1.1" + dependencies: + leven: ^3.1.0 + checksum: 320c5e36351704f6284879752de8acc604bf2f98c9fc9389a12e1b9f3a74bd57e2776d2859d5bcbf65a8127f705ea4ee9d06d428d9be389c80caf343f7981d14 + languageName: node + linkType: hard + "levn@npm:^0.3.0, levn@npm:~0.3.0": version: 0.3.0 resolution: "levn@npm:0.3.0" @@ -24272,6 +25007,15 @@ __metadata: languageName: node linkType: hard +"magic-string@npm:^0.27.0": + version: 0.27.0 + resolution: "magic-string@npm:0.27.0" + dependencies: + "@jridgewell/sourcemap-codec": ^1.4.13 + checksum: cddacfea14441ca57ae8a307bc3cf90bac69efaa4138dd9a80804cffc2759bf06f32da3a293fb13eaa96334b7d45b7768a34f1d226afae25d2f05b05a3bb37d8 + languageName: node + linkType: hard + "mailchimp-api-v3@npm:^1.14.0": version: 1.15.0 resolution: "mailchimp-api-v3@npm:1.15.0" @@ -27964,6 +28708,15 @@ __metadata: languageName: node linkType: hard +"pascalcase@npm:^2.0.0": + version: 2.0.0 + resolution: "pascalcase@npm:2.0.0" + dependencies: + camelcase: ^6.2.1 + checksum: c43658cd7bea0dc2b4362b0b15c803ee976fb6f981addab8452e1ad67cd768739979b7e687df38452f3743af34fe00f0292234f5aceb73457babb3fbdd585863 + languageName: node + linkType: hard + "passport-http-bearer@npm:^1.0.1": version: 1.0.1 resolution: "passport-http-bearer@npm:1.0.1" @@ -29413,6 +30166,13 @@ __metadata: languageName: node linkType: hard +"proxy-from-env@npm:^1.1.0": + version: 1.1.0 + resolution: "proxy-from-env@npm:1.1.0" + checksum: fe7dd8b1bdbbbea18d1459107729c3e4a2243ca870d26d34c2c1bcd3e4425b7bcc5112362df2d93cc7fb9746f6142b5e272fd1cc5c86ddf8580175186f6ad42b + languageName: node + linkType: hard + "prr@npm:~1.0.1": version: 1.0.1 resolution: "prr@npm:1.0.1" @@ -30456,6 +31216,15 @@ __metadata: languageName: node linkType: hard +"regenerate-unicode-properties@npm:^10.1.0": + version: 10.1.0 + resolution: "regenerate-unicode-properties@npm:10.1.0" + dependencies: + regenerate: ^1.4.2 + checksum: 17818ea6f67c5a4884b9e18842edc4b3838a12f62e24f843e80fbb6d8cb649274b5b86d98bb02075074e02021850e597a92ff6b58bbe5caba4bf5fd8e4e38b56 + languageName: node + linkType: hard + "regenerate@npm:^1.4.2": version: 1.4.2 resolution: "regenerate@npm:1.4.2" @@ -30486,6 +31255,15 @@ __metadata: languageName: node linkType: hard +"regenerator-transform@npm:^0.15.1": + version: 0.15.1 + resolution: "regenerator-transform@npm:0.15.1" + dependencies: + "@babel/runtime": ^7.8.4 + checksum: 6588e0c454e92ed6c2b3ed7ab24f61270aef47ae7052eceb5367cc15658948a2e84fdd6849f7c96e561d1f8a7474dc4c292166792e07498fdde226299b9ff374 + languageName: node + linkType: hard + "regex-not@npm:^1.0.0, regex-not@npm:^1.0.2": version: 1.0.2 resolution: "regex-not@npm:1.0.2" @@ -30535,6 +31313,20 @@ __metadata: languageName: node linkType: hard +"regexpu-core@npm:^5.2.1": + version: 5.3.0 + resolution: "regexpu-core@npm:5.3.0" + dependencies: + "@babel/regjsgen": ^0.8.0 + regenerate: ^1.4.2 + regenerate-unicode-properties: ^10.1.0 + regjsparser: ^0.9.1 + unicode-match-property-ecmascript: ^2.0.0 + unicode-match-property-value-ecmascript: ^2.1.0 + checksum: 56d6c2858085b15df622d6346b47aa73f8c341fe70c8fec46dfff49a0213d9d4f216f6d771364f9e35311b8d99d829dd2dded909fdea8441f5a344bb1cb9c744 + languageName: node + linkType: hard + "registry-auth-token@npm:^4.0.0": version: 4.2.2 resolution: "registry-auth-token@npm:4.2.2" @@ -30571,6 +31363,17 @@ __metadata: languageName: node linkType: hard +"regjsparser@npm:^0.9.1": + version: 0.9.1 + resolution: "regjsparser@npm:0.9.1" + dependencies: + jsesc: ~0.5.0 + bin: + regjsparser: bin/parser + checksum: fe44fcf19a99fe4f92809b0b6179530e5ef313ff7f87df143b08ce9a2eb3c4b6189b43735d645be6e8f4033bfb015ed1ca54f0583bc7561bed53fd379feb8225 + languageName: node + linkType: hard + "relateurl@npm:^0.2.7": version: 0.2.7 resolution: "relateurl@npm:0.2.7" @@ -30917,7 +31720,7 @@ __metadata: languageName: node linkType: hard -"resolve@npm:^1.10.0, resolve@npm:^1.12.0, resolve@npm:^1.14.2, resolve@npm:^1.17.0, resolve@npm:^1.18.1, resolve@npm:^1.19.0, resolve@npm:^1.20.0, resolve@npm:^1.22.0, resolve@npm:^1.3.2": +"resolve@npm:^1.10.0, resolve@npm:^1.12.0, resolve@npm:^1.14.2, resolve@npm:^1.17.0, resolve@npm:^1.18.1, resolve@npm:^1.19.0, resolve@npm:^1.20.0, resolve@npm:^1.22.0, resolve@npm:^1.22.1, resolve@npm:^1.3.2": version: 1.22.1 resolution: "resolve@npm:1.22.1" dependencies: @@ -30959,7 +31762,7 @@ __metadata: languageName: node linkType: hard -"resolve@patch:resolve@^1.10.0#~builtin, resolve@patch:resolve@^1.12.0#~builtin, resolve@patch:resolve@^1.14.2#~builtin, resolve@patch:resolve@^1.17.0#~builtin, resolve@patch:resolve@^1.18.1#~builtin, resolve@patch:resolve@^1.19.0#~builtin, resolve@patch:resolve@^1.20.0#~builtin, resolve@patch:resolve@^1.22.0#~builtin, resolve@patch:resolve@^1.3.2#~builtin": +"resolve@patch:resolve@^1.10.0#~builtin, resolve@patch:resolve@^1.12.0#~builtin, resolve@patch:resolve@^1.14.2#~builtin, resolve@patch:resolve@^1.17.0#~builtin, resolve@patch:resolve@^1.18.1#~builtin, resolve@patch:resolve@^1.19.0#~builtin, resolve@patch:resolve@^1.20.0#~builtin, resolve@patch:resolve@^1.22.0#~builtin, resolve@patch:resolve@^1.22.1#~builtin, resolve@patch:resolve@^1.3.2#~builtin": version: 1.22.1 resolution: "resolve@patch:resolve@npm%3A1.22.1#~builtin::version=1.22.1&hash=07638b" dependencies: @@ -31137,7 +31940,7 @@ __metadata: languageName: node linkType: hard -"rollup-plugin-terser@npm:^7.0.2": +"rollup-plugin-terser@npm:7.0.2, rollup-plugin-terser@npm:^7.0.2": version: 7.0.2 resolution: "rollup-plugin-terser@npm:7.0.2" dependencies: @@ -31176,6 +31979,20 @@ __metadata: languageName: node linkType: hard +"rollup@npm:3.9.1": + version: 3.9.1 + resolution: "rollup@npm:3.9.1" + dependencies: + fsevents: ~2.3.2 + dependenciesMeta: + fsevents: + optional: true + bin: + rollup: dist/bin/rollup + checksum: 48875f9414b17dda1f3a8eea3a2cfffbe847451f986e0c4f6363f06cde05d508730f29486d3f25f27db28dc76bb411a23959b826b1054d3848bab0d84a471fdd + languageName: node + linkType: hard + "rollup@npm:^2.35.1": version: 2.76.0 resolution: "rollup@npm:2.76.0" @@ -34271,6 +35088,13 @@ __metadata: languageName: node linkType: hard +"tslib@npm:2.3.1, tslib@npm:~2.3.0": + version: 2.3.1 + resolution: "tslib@npm:2.3.1" + checksum: 4efd888895bdb3b987086b2b7793ad1013566f882b0eb7a328384e5ecc0d71cafb16bbeab3196200cbf7f01a73ccc25acc2f131d4ea6ee959be7436a8a306482 + languageName: node + linkType: hard + "tslib@npm:^1, tslib@npm:^1.10.0, tslib@npm:^1.8.1, tslib@npm:^1.9.0": version: 1.14.1 resolution: "tslib@npm:1.14.1" @@ -34306,13 +35130,6 @@ __metadata: languageName: node linkType: hard -"tslib@npm:~2.3.0": - version: 2.3.1 - resolution: "tslib@npm:2.3.1" - checksum: 4efd888895bdb3b987086b2b7793ad1013566f882b0eb7a328384e5ecc0d71cafb16bbeab3196200cbf7f01a73ccc25acc2f131d4ea6ee959be7436a8a306482 - languageName: node - linkType: hard - "tsscmp@npm:1.0.6": version: 1.0.6 resolution: "tsscmp@npm:1.0.6" @@ -34908,6 +35725,13 @@ __metadata: languageName: node linkType: hard +"unicode-match-property-value-ecmascript@npm:^2.1.0": + version: 2.1.0 + resolution: "unicode-match-property-value-ecmascript@npm:2.1.0" + checksum: f5b9499b9e0ffdc6027b744d528f17ec27dd7c15da03254ed06851feec47e0531f20d410910c8a49af4a6a190f4978413794c8d75ce112950b56d583b5d5c7f2 + languageName: node + linkType: hard + "unicode-property-aliases-ecmascript@npm:^2.0.0": version: 2.0.0 resolution: "unicode-property-aliases-ecmascript@npm:2.0.0"