Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 8be147d

Browse files
committedMar 5, 2024
feat: add --ky option
Generate a client that uses this library: https://github.com/sindresorhus/ky
1 parent 2b6db23 commit 8be147d

File tree

10 files changed

+157
-2
lines changed

10 files changed

+157
-2
lines changed
 

‎README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ Options:
6868
--disableStrictSSL disabled strict SSL (default: false)
6969
--disableProxy disabled proxy (default: false)
7070
--axios generate axios http client (default: false)
71+
--ky generate ky http client (default: false)
7172
--unwrap-response-data unwrap the data item from the response (default: false)
7273
--disable-throw-on-error Do not throw an error when response.ok is not true (default: false)
7374
--single-http-client Ability to send HttpClient instance to Api constructor (default: false)
@@ -124,7 +125,7 @@ generateApi({
124125
// ...
125126
},
126127
templates: path.resolve(process.cwd(), './api-templates'),
127-
httpClientType: "axios", // or "fetch"
128+
httpClientType: "axios", // or "fetch" or "ky"
128129
defaultResponseAsSuccess: false,
129130
generateClient: true,
130131
generateRouteTypes: false,

‎index.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,11 @@ const program = cli({
162162
description: 'generate axios http client',
163163
default: codeGenBaseConfig.httpClientType === HTTP_CLIENT.AXIOS,
164164
},
165+
{
166+
flags: '--ky',
167+
description: 'generate axios http client',
168+
default: codeGenBaseConfig.httpClientType === HTTP_CLIENT.KY,
169+
},
165170
{
166171
flags: '--unwrap-response-data',
167172
description: 'unwrap the data item from the response',
@@ -324,7 +329,7 @@ const main = async () => {
324329
url: options.path,
325330
generateRouteTypes: options.routeTypes,
326331
generateClient: !!(options.axios || options.client),
327-
httpClientType: options.axios ? HTTP_CLIENT.AXIOS : HTTP_CLIENT.FETCH,
332+
httpClientType: options.axios ? HTTP_CLIENT.AXIOS : options.ky ? HTTP_CLIENT.KY : HTTP_CLIENT.FETCH,
328333
input: resolve(process.cwd(), options.path),
329334
output: resolve(process.cwd(), options.output || '.'),
330335
...customConfig,

‎package-lock.json

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json

+1
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@
8585
"dotenv": "^16.3.1",
8686
"git-diff": "^2.0.6",
8787
"husky": "^8.0.3",
88+
"ky": "^1.2.2",
8889
"pretty-quick": "^3.1.3",
8990
"rimraf": "^5.0.1"
9091
},

‎src/constants.js

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ const SCHEMA_TYPES = {
2828
const HTTP_CLIENT = {
2929
FETCH: 'fetch',
3030
AXIOS: 'axios',
31+
KY: 'ky',
3132
};
3233

3334
const PROJECT_VERSION = packageJson.version;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
<%
2+
const { apiConfig, generateResponses, config } = it;
3+
%>
4+
5+
import type { KyInstance, Options as KyOptions, KyResponse } from "ky";
6+
import ky from "ky";
7+
8+
export type QueryParamsType = Record<string | number, any>;
9+
export type ResponseFormat = keyof Omit<Body, "body" | "bodyUsed">;
10+
11+
export interface FullRequestParams extends Omit<KyOptions, "json" | "body"> {
12+
/** set parameter to `true` for call `securityWorker` for this request */
13+
secure?: boolean;
14+
/** request path */
15+
path: string;
16+
/** content type of request body */
17+
type?: ContentType;
18+
/** query params */
19+
query?: QueryParamsType;
20+
/** format of response (i.e. response.json() -> format: "json") */
21+
format?: ResponseFormat;
22+
/** request body */
23+
body?: unknown;
24+
}
25+
26+
export type RequestParams = Omit<FullRequestParams, "body" | "method" | "query" | "path">;
27+
28+
export interface ApiConfig<SecurityDataType = unknown> extends Omit<KyOptions, "data" | "cancelToken"> {
29+
securityWorker?: (securityData: SecurityDataType | null) => Promise<KyOptions | void> | KyOptions | void;
30+
secure?: boolean;
31+
format?: ResponseType;
32+
}
33+
34+
export enum ContentType {
35+
Json = "application/json",
36+
FormData = "multipart/form-data",
37+
UrlEncoded = "application/x-www-form-urlencoded",
38+
Text = "text/plain",
39+
}
40+
41+
export class HttpClient<SecurityDataType = unknown> {
42+
public instance: KyInstance;
43+
private securityData: SecurityDataType | null = null;
44+
private securityWorker?: ApiConfig<SecurityDataType>["securityWorker"];
45+
private secure?: boolean;
46+
private format?: ResponseType;
47+
48+
constructor({ securityWorker, secure, format, ...options }: ApiConfig<SecurityDataType> = {}) {
49+
this.instance = axios.create({ ...options, prefixUrl: options.prefixUrl || "<%~ apiConfig.baseUrl %>" })
50+
this.secure = secure;
51+
this.format = format;
52+
this.securityWorker = securityWorker;
53+
}
54+
55+
public setSecurityData = (data: SecurityDataType | null) => {
56+
this.securityData = data
57+
}
58+
59+
protected mergeRequestParams(params1: KyOptions, params2?: KyOptions): KyOptions {
60+
return {
61+
...params1,
62+
...params2,
63+
headers: {
64+
...(params1.headers),
65+
...(params2 && params2.headers),
66+
},
67+
};
68+
}
69+
70+
protected stringifyFormItem(formItem: unknown) {
71+
if (typeof formItem === "object" && formItem !== null) {
72+
return JSON.stringify(formItem);
73+
} else {
74+
return `${formItem}`;
75+
}
76+
}
77+
78+
protected createFormData(input: Record<string, unknown>): FormData {
79+
return Object.keys(input || {}).reduce((formData, key) => {
80+
const property = input[key];
81+
const propertyContent: any[] = (property instanceof Array) ? property : [property]
82+
83+
for (const formItem of propertyContent) {
84+
const isFileType = formItem instanceof Blob || formItem instanceof File;
85+
formData.append(
86+
key,
87+
isFileType ? formItem : this.stringifyFormItem(formItem)
88+
);
89+
}
90+
91+
return formData;
92+
}, new FormData());
93+
}
94+
95+
public request = async <T = any, _E = any>({
96+
secure,
97+
path,
98+
type,
99+
query,
100+
format,
101+
body,
102+
...params
103+
<% if (config.unwrapResponseData) { %>
104+
}: FullRequestParams): Promise<T> => {
105+
<% } else { %>
106+
}: FullRequestParams): KyResponse<T> => {
107+
<% } %>
108+
const secureParams = ((typeof secure === 'boolean' ? secure : this.secure) && this.securityWorker && (await this.securityWorker(this.securityData))) || {};
109+
const requestParams = this.mergeRequestParams(params, secureParams);
110+
const responseFormat = (format || this.format) || undefined;
111+
112+
if (type === ContentType.FormData && body && body !== null && typeof body === "object") {
113+
body = this.createFormData(body as Record<string, unknown>);
114+
}
115+
116+
if (type === ContentType.Text && body && body !== null && typeof body !== "string") {
117+
body = JSON.stringify(body);
118+
}
119+
120+
return this.instance.request({
121+
...requestParams,
122+
headers: {
123+
...(requestParams.headers || {}),
124+
...(type && type !== ContentType.FormData ? { "Content-Type": type } : {}),
125+
},
126+
params: query,
127+
responseType: responseFormat,
128+
data: body,
129+
url: path,
130+
<% if (config.unwrapResponseData) { %>
131+
}).json();
132+
<% } else { %>
133+
});
134+
<% } %>
135+
};
136+
}

‎templates/default/api.ejs

+2
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ const descriptionLines = _.compact([
2828

2929
<% if (config.httpClientType === config.constants.HTTP_CLIENT.AXIOS) { %> import type { AxiosRequestConfig, AxiosResponse } from "axios"; <% } %>
3030

31+
<% if (config.httpClientType === config.constants.HTTP_CLIENT.KY) { %> import type { KyResponse } from "ky"; <% } %>
32+
3133
<% if (descriptionLines.length) { %>
3234
/**
3335
<% descriptionLines.forEach((descriptionLine) => { %>

‎templates/default/procedure-call.ejs

+3
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ const describeReturnType = () => {
7272
case HTTP_CLIENT.AXIOS: {
7373
return `Promise<AxiosResponse<${type}>>`
7474
}
75+
case HTTP_CLIENT.KY: {
76+
return `KyResponse<${type}>`
77+
}
7578
default: {
7679
return `Promise<HttpResponse<${type}, ${errorType}>`
7780
}

‎templates/modular/api.ejs

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ const dataContracts = _.map(modelTypes, "name");
88

99
<% if (config.httpClientType === config.constants.HTTP_CLIENT.AXIOS) { %> import type { AxiosRequestConfig, AxiosResponse } from "axios"; <% } %>
1010

11+
<% if (config.httpClientType === config.constants.HTTP_CLIENT.AXIOS) { %> import type { KyResponse } from "ky"; <% } %>
12+
1113
import { HttpClient, RequestParams, ContentType, HttpResponse } from "./<%~ config.fileNames.httpClient %>";
1214
<% if (dataContracts.length) { %>
1315
import { <%~ dataContracts.join(", ") %> } from "./<%~ config.fileNames.dataContracts %>"

‎templates/modular/procedure-call.ejs

+3
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ const describeReturnType = () => {
7272
case HTTP_CLIENT.AXIOS: {
7373
return `Promise<AxiosResponse<${type}>>`
7474
}
75+
case HTTP_CLIENT.KY: {
76+
return `KyResponse<${type}>`
77+
}
7578
default: {
7679
return `Promise<HttpResponse<${type}, ${errorType}>`
7780
}

0 commit comments

Comments
 (0)
Please sign in to comment.