Skip to content

Commit 0d17423

Browse files
committed
feat: add ofetch http client
1 parent 24c9fe2 commit 0d17423

File tree

12 files changed

+59290
-4
lines changed

12 files changed

+59290
-4
lines changed

README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ Options:
4040
--module-name-index <number> determines which path index should be used for routes separation (example: GET:/fruits/getFruit -> index:0 -> moduleName -> fruits) (default: 0)
4141
--module-name-first-tag splits routes based on the first tag (default: false)
4242
--axios generate axios http client (default: false)
43+
--ofetch generate ofetch http client (default: false)
4344
--unwrap-response-data unwrap the data item from the response (default: false)
4445
--disable-throw-on-error Do not throw an error when response.ok is not true (default: false)
4546
--single-http-client Ability to send HttpClient instance to Api constructor (default: false)
@@ -62,7 +63,7 @@ Commands:
6263
generate-templates Generate ".ejs" templates needed for generate api
6364
-o, --output <string> output path of generated templates
6465
-m, --modular generate templates needed to separate files for http client, data contracts, and routes (default: false)
65-
--http-client <string> http client type (possible values: "fetch", "axios") (default: "fetch")
66+
--http-client <string> http client type (possible values: "fetch", "axios", "ofetch") (default: "fetch")
6667
-c, --clean-output clean output folder before generate template. WARNING: May cause data loss (default: false)
6768
-r, --rewrite rewrite content in existing templates (default: false)
6869
--silent Output only errors to console (default: false)

index.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,11 @@ const generateCommand = defineCommand({
191191
description: "generate axios http client",
192192
default: false,
193193
},
194+
ofetch: {
195+
type: "boolean",
196+
description: "generate ofetch http client",
197+
default: false,
198+
},
194199
"unwrap-response-data": {
195200
type: "boolean",
196201
description: "unwrap the data item from the response",
@@ -318,7 +323,9 @@ const generateCommand = defineCommand({
318323
httpClientType:
319324
args["http-client"] || args.axios
320325
? HTTP_CLIENT.AXIOS
321-
: HTTP_CLIENT.FETCH,
326+
: args.ofetch
327+
? HTTP_CLIENT.OFETCH
328+
: HTTP_CLIENT.FETCH,
322329
input: path.resolve(process.cwd(), args.path as string),
323330
modular: args.modular,
324331
moduleNameFirstTag: args["module-name-first-tag"],

src/constants.ts

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export const FILE_PREFIX = `/* eslint-disable */
1717

1818
export const HTTP_CLIENT = {
1919
FETCH: "fetch",
20+
OFETCH: "ofetch",
2021
AXIOS: "axios",
2122
} as const;
2223

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
<%
2+
const { apiConfig, generateResponses, config } = it;
3+
%>
4+
5+
import type { $Fetch, FetchOptions } from 'ofetch'
6+
import { $fetch } from 'ofetch'
7+
8+
export type QueryParamsType = Record<string | number, any>;
9+
export type ResponseFormat = keyof Omit<Body, "body" | "bodyUsed">;
10+
11+
export interface CustomFetchOptions extends FetchOptions {
12+
/** set parameter to `true` for call `securityWorker` for this request */
13+
secure?: boolean
14+
}
15+
16+
export type RequestParams = Omit<CustomFetchOptions, 'body' | 'method'>
17+
18+
export interface ApiConfig<SecurityDataType = unknown> {
19+
baseURL?: string;
20+
basePath?: string;
21+
baseApiParams?: Omit<RequestParams, "baseURL" | "cancelToken" | "signal">;
22+
securityWorker?: (securityData: SecurityDataType | null) => Promise<RequestParams | void> | RequestParams | void;
23+
customFetch?: $Fetch;
24+
}
25+
26+
type CancelToken = Symbol | string | number;
27+
28+
export enum ContentType {
29+
Json = "application/json",
30+
FormData = "multipart/form-data",
31+
UrlEncoded = "application/x-www-form-urlencoded",
32+
}
33+
34+
export class HttpClient<SecurityDataType = unknown> {
35+
public baseURL: string = "<%~ apiConfig.baseUrl %>";
36+
private securityData: SecurityDataType | null = null;
37+
private securityWorker?: ApiConfig<SecurityDataType>["securityWorker"];
38+
private abortControllers = new Map<CancelToken, AbortController>();
39+
private customFetch = (url: string, fetchParams: FetchOptions) => $fetch(url, fetchParams)
40+
41+
private baseApiParams: RequestParams = {
42+
credentials: 'same-origin',
43+
headers: {},
44+
redirect: 'follow',
45+
referrerPolicy: 'no-referrer',
46+
}
47+
48+
constructor(apiConfig: ApiConfig<SecurityDataType> = {}) {
49+
Object.assign(this, apiConfig);
50+
}
51+
52+
public setSecurityData = (data: SecurityDataType | null) => {
53+
this.securityData = data;
54+
}
55+
56+
protected mergeRequestParams(params1: RequestParams, params2?: RequestParams): RequestParams {
57+
return {
58+
...this.baseApiParams,
59+
...params1,
60+
...(params2 || {}),
61+
headers: {
62+
...(this.baseApiParams.headers || {}),
63+
...(params1.headers || {}),
64+
...((params2 && params2.headers) || {}),
65+
},
66+
};
67+
}
68+
69+
protected createAbortSignal = (cancelToken: CancelToken): AbortSignal | undefined => {
70+
if (this.abortControllers.has(cancelToken)) {
71+
const abortController = this.abortControllers.get(cancelToken);
72+
if (abortController) {
73+
return abortController.signal;
74+
}
75+
return void 0;
76+
}
77+
78+
const abortController = new AbortController();
79+
this.abortControllers.set(cancelToken, abortController);
80+
return abortController.signal;
81+
}
82+
83+
public abortRequest = (cancelToken: CancelToken) => {
84+
const abortController = this.abortControllers.get(cancelToken)
85+
86+
if (abortController) {
87+
abortController.abort();
88+
this.abortControllers.delete(cancelToken);
89+
}
90+
}
91+
92+
public request = async <T = any>(url: string, {
93+
body,
94+
secure,
95+
method,
96+
baseURL,
97+
signal,
98+
params,
99+
...options
100+
<% if (config.unwrapResponseData) { %>
101+
}: CustomFetchOptions): Promise<T> => {
102+
<% } else { %>
103+
}: CustomFetchOptions): Promise<T> => {
104+
<% } %>
105+
const secureParams = ((typeof secure === 'boolean' ? secure : this.baseApiParams.secure) && this.securityWorker && await this.securityWorker(this.securityData)) || {};
106+
const requestOptions = this.mergeRequestParams(options, secureParams)
107+
108+
return this.customFetch(
109+
`${baseURL || this.baseURL || ""}${this.basePath ? `${this.basePath}` : ''}${url}`,
110+
{
111+
params,
112+
method,
113+
...requestOptions,
114+
signal,
115+
body,
116+
}
117+
<% if (config.unwrapResponseData) { %>
118+
).then((response: T) => response.data)
119+
<% } else { %>
120+
).then((response: T) => response)
121+
<% } %>
122+
};
123+
}

templates/default/procedure-call.ejs

+7
Original file line numberDiff line numberDiff line change
@@ -88,13 +88,20 @@ const describeReturnType = () => {
8888

8989
*/
9090
<%~ route.routeName.usage %><%~ route.namespace ? ': ' : ' = ' %>(<%~ wrapperArgs %>)<%~ config.toJS ? `: ${describeReturnType()}` : "" %> =>
91+
<% if (config.httpClientType === config.constants.HTTP_CLIENT.OFETCH) { %>
92+
<%~ config.singleHttpClient ? 'this.http.request' : 'this.request' %><<%~ type %>>(`<%~ path %>`, {
93+
<% } %>
94+
<% if (config.httpClientType !== config.constants.HTTP_CLIENT.OFETCH) { %>
9195
<%~ config.singleHttpClient ? 'this.http.request' : 'this.request' %><<%~ type %>, <%~ errorType %>>({
9296
path: `<%~ path %>`,
97+
<% } %>
9398
method: '<%~ _.upperCase(method) %>',
9499
<%~ queryTmpl ? `query: ${queryTmpl},` : '' %>
95100
<%~ bodyTmpl ? `body: ${bodyTmpl},` : '' %>
96101
<%~ securityTmpl ? `secure: ${securityTmpl},` : '' %>
102+
<% if (config.httpClientType !== config.constants.HTTP_CLIENT.OFETCH) { %>
97103
<%~ bodyContentKindTmpl ? `type: ${bodyContentKindTmpl},` : '' %>
98104
<%~ responseFormatTmpl ? `format: ${responseFormatTmpl},` : '' %>
105+
<% } %>
99106
...<%~ _.get(requestConfigParam, "name") %>,
100107
})<%~ route.namespace ? ',' : '' %>

0 commit comments

Comments
 (0)