diff --git a/examples/petstore-v2.ts b/examples/petstore-v2.ts index 6c5cc83..cb25d56 100644 --- a/examples/petstore-v2.ts +++ b/examples/petstore-v2.ts @@ -33,10 +33,8 @@ export class ApiClient { async ParseError(rep: Response) { try { - // try to parse domain error from response body return await rep.json() } catch (e) { - // otherwise return response as is throw rep } } @@ -58,7 +56,7 @@ export class ApiClient { let body: FormData | URLSearchParams | string | undefined = undefined if (ct === "multipart/form-data" || ct === "application/x-www-form-urlencoded") { - headers.delete("content-type") // https://stackoverflow.com/a/61053359/3664464 + headers.delete("content-type") body = ct === "multipart/form-data" ? new FormData() : new URLSearchParams() for (const [k, v] of Object.entries(opts.body as Record)) { body.append(k, v) diff --git a/examples/petstore-v3.ts b/examples/petstore-v3.ts index 32041aa..d9e2adc 100644 --- a/examples/petstore-v3.ts +++ b/examples/petstore-v3.ts @@ -33,10 +33,8 @@ export class ApiClient { async ParseError(rep: Response) { try { - // try to parse domain error from response body return await rep.json() } catch (e) { - // otherwise return response as is throw rep } } @@ -58,7 +56,7 @@ export class ApiClient { let body: FormData | URLSearchParams | string | undefined = undefined if (ct === "multipart/form-data" || ct === "application/x-www-form-urlencoded") { - headers.delete("content-type") // https://stackoverflow.com/a/61053359/3664464 + headers.delete("content-type") body = ct === "multipart/form-data" ? new FormData() : new URLSearchParams() for (const [k, v] of Object.entries(opts.body as Record)) { body.append(k, v) diff --git a/readme.md b/readme.md index dcfdd87..a1db186 100644 --- a/readme.md +++ b/readme.md @@ -3,10 +3,10 @@
[version](https://npmjs.org/package/apigen-ts) -[test status](https://github.com/vladkens/apigen-ts/actions) [size](https://packagephobia.now.sh/result?p=apigen-ts) [downloads](https://npmjs.org/package/apigen-ts) [license](https://github.com/vladkens/apigen-ts/blob/main/LICENSE) +[donate](https://buymeacoffee.com/vladkens)
@@ -15,7 +15,7 @@
- TypeScript api client generator from OpenAPI specification + TypeScript HTTP Client Generator from OpenAPI Specification
## Features @@ -37,7 +37,7 @@ yarn install -D apigen-ts ## Usage -### Generate +### 1. Generate ```sh # From url @@ -49,9 +49,9 @@ yarn apigen-ts ./openapi.json ./api-client.ts Run `yarn apigen-ts --help` for more options. See examples of generated clients [here](./examples/). -### Import +### 2. Import -```typescript +```ts import { ApiClient } from "./api-client" const api = new ApiClient({ @@ -60,9 +60,9 @@ const api = new ApiClient({ }) ``` -### Use +### 3. Use -```typescript +```ts // GET /pet/{petId} await api.pet.getPetById(1) // -> Pet @@ -77,18 +77,29 @@ await api.user.updateUser("username", { firstName: "John" }) ### Login flow -```typescript +```ts const { token } = await api.auth.login({ usename, password }) api.Config.headers = { Authorization: token } await api.protectedRoute.get() // here authenticated ``` +### Automatic date parsing + +```sh +yarn apigen-ts ./openapi.json ./api-client.ts --parse-dates +``` + +```ts +const pet = await api.pet.getPetById(1) +const createdAt: Date = pet.createdAt // date parsed from string with format=date-time +``` + ### NodeJS API Create file like `apigen.mjs` with content: -```javascript +```js import { apigen } from "apigen-ts" await apigen({ diff --git a/scripts/guru-check.ts b/scripts/guru-check.ts index 05109c3..c65d0f6 100644 --- a/scripts/guru-check.ts +++ b/scripts/guru-check.ts @@ -1,7 +1,5 @@ import { enumerate, filterNullable } from "array-utils-ts" -import { exec } from "child_process" import fs from "fs/promises" -import util from "node:util" import { loadSchema } from "../src/generator" import { apigen } from "../src/main" @@ -53,7 +51,7 @@ const generateClients = async () => { for (const [i, file] of enumerate(files, 1)) { if (debugId && i !== debugId) continue - const src = `${BaseDir}/specs/${file}` + const src = await fs.realpath(`${BaseDir}/specs/${file}`) const out = `${BaseDir}/clients/${file.replace(".json", ".ts")}` const doc = JSON.parse(await fs.readFile(src, "utf-8")) @@ -64,7 +62,7 @@ const generateClients = async () => { const tag = `[${i.toString().padStart(len, " ")}/${files.length}] (${ver}) ${src}` try { - await apigen({ source: src, output: out, parseDates: true }) + await apigen({ source: `file://${src}`, output: out, parseDates: true }) console.log(`${tag} generated`) } catch (err) { console.log(tag, "failed", err) @@ -82,9 +80,12 @@ const generateClients = async () => { console.log(`versions: ${Object.entries(versions).map((x) => x.join(" - ")).join(", ")}`) if (failed.length) console.log(`failed ids: ${failed.join(", ")}`) - const cmd = `yarn tsc --noEmit ${BaseDir}/clients/*.ts` - const { stdout, stderr } = await util.promisify(exec)(cmd) - console.log(stdout, stderr) + // NODE_OPTIONS="--max-old-space-size=8192" yarn tsc --noEmit ${BaseDir}/clients/*.ts + // const cmd = `yarn tsc --noEmit ${BaseDir}/clients/*.ts` + // const { stdout, stderr } = await util.promisify(exec)(cmd, { + // env: { ...process.env, NODE_OPTIONS: "--max-old-space-size=8192" }, + // }) + // console.log(stdout, stderr) } const main = async () => { diff --git a/src/_template.ts b/src/_template.ts index 72d0d47..ebc4719 100644 --- a/src/_template.ts +++ b/src/_template.ts @@ -57,7 +57,8 @@ export class ApiClient { let body: FormData | URLSearchParams | string | undefined = undefined if (ct === "multipart/form-data" || ct === "application/x-www-form-urlencoded") { - headers.delete("content-type") // https://stackoverflow.com/a/61053359/3664464 + // https://stackoverflow.com/a/61053359/3664464 + headers.delete("content-type") body = ct === "multipart/form-data" ? new FormData() : new URLSearchParams() for (const [k, v] of Object.entries(opts.body as Record)) { body.append(k, v) diff --git a/src/generator.ts b/src/generator.ts index 5c43b3a..842edc7 100644 --- a/src/generator.ts +++ b/src/generator.ts @@ -232,6 +232,8 @@ export const generateAst = async (ctx: Context) => { } export const loadSchema = async (url: string, upgrade = true): Promise => { + if (url.startsWith("file://")) url = url.substring(7) + const { bundle } = await redocly.bundle({ ref: url, config: await redocly.createConfig({}), diff --git a/src/main.ts b/src/main.ts index d7ae067..ed04530 100644 --- a/src/main.ts +++ b/src/main.ts @@ -17,16 +17,17 @@ export const apigen = async (config: Partial & Pick !x.startsWith("// Note: ")), + // remove all comments expect which starts with "// apigen:" + ...file.split("\n").filter((x) => !/\s*\/\/\s(?!apigen:)/.test(x)), ].join("\n") if (!ctx.parseDates) { + code = code.replace(/\s*ISO_FORMAT\s=.+$/gm, "") code = code.replace(/PopulateDates.+?\n\s{2}\}/s, "") code = code.replace(/this.PopulateDates\((.+)\)/, "$1") } - // broken types by design, but typescript still can print it - // need to keep original formatting & comments of the template + // broken type by design, need to keep original formatting (ts still can print it) code = code.replace("// apigen:modules", printCode(modules as unknown as ts.Statement[])) code = code.replace("// apigen:types", printCode(types)) code = code.replace("class ApiClient", `class ${ctx.name}`)