Skip to content

Commit d529003

Browse files
committed
feat(fetch): add fetch client
1 parent 4931bb5 commit d529003

6 files changed

Lines changed: 114 additions & 26 deletions

File tree

.vscode/settings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
"custom-instance",
1111
"editor",
1212
"input-transformer",
13-
"zod"
13+
"zod",
14+
"fetch"
1415
],
1516
"cSpell.words": [
1617
"orval",

orval.config.ts

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,39 @@ export default defineConfig({
55
input: { target: "./openapi.yaml", validation: false },
66
output: { client: "zod", mode: "tags-split", target: "./src/api", fileExtension: ".zod.ts" },
77
},
8-
api: {
8+
fetch: {
99
input: { target: "./openapi.yaml", validation: false },
1010
output: {
11+
clean: true,
1112
mode: "tags-split",
12-
target: "./src/api/api.ts",
13+
target: "./src/api",
14+
schemas: "./src/api/api-schemas",
15+
fileExtension: ".fetch.ts",
1316
client: "react-query",
14-
baseUrl: "",
17+
httpClient: "fetch",
1518
override: {
1619
operationName: (operation) => operation["x-semantic-name"],
17-
mutator: { path: "./src/custom-instance.ts", name: "customInstance" },
20+
mutator: { path: "./src/custom-fetch.ts", name: "customFetch" },
21+
query: {
22+
useQuery: true,
23+
useSuspenseQuery: true,
24+
useInvalidate: true,
25+
shouldSplitQueryKey: true,
26+
},
27+
},
28+
},
29+
},
30+
axios: {
31+
input: { target: "./openapi.yaml", validation: false },
32+
output: {
33+
mode: "tags-split",
34+
target: "./src/api",
35+
schemas: "./src/api/api-schemas",
36+
client: "react-query",
37+
httpClient: "axios",
38+
override: {
39+
operationName: (operation) => operation["x-semantic-name"],
40+
mutator: { path: "./src/custom-axios.ts", name: "customInstance" },
1841
query: {
1942
useQuery: true,
2043
useSuspenseQuery: true,
Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -32,19 +32,6 @@ export const setBaseUrl = (baseUrl: string) => {
3232
export const customInstance = <TReturn>(
3333
config: AxiosRequestConfig,
3434
options?: AxiosRequestConfig,
35-
): Promise<TReturn> => {
36-
const source = Axios.CancelToken.source();
37-
38-
const promise = AXIOS_INSTANCE({ ...config, ...options, cancelToken: source.token }).then(
39-
({ data }) => data,
40-
);
41-
42-
// @ts-expect-error need to add a cancel method to the promise
43-
promise.cancel = () => {
44-
source.cancel("Query was cancelled");
45-
};
46-
47-
return promise;
48-
};
35+
): Promise<TReturn> => AXIOS_INSTANCE({ ...config, ...options }).then(({ data }) => data);
4936

5037
export type ErrorType<TError> = AxiosError<TError>;

src/custom-fetch.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Custom fetch instance with runtime base URL configuration
2+
// Based on Orval docs: https://orval.dev/guides/custom-client
3+
4+
let baseURL = "";
5+
6+
/**
7+
* Set the base URL for all fetch requests
8+
* @param url - The base URL to use for all API calls
9+
*/
10+
export const setBaseURL = (url: string) => {
11+
baseURL = url.replace(/\/$/, ""); // Remove trailing slash
12+
};
13+
14+
/**
15+
* Get the current base URL
16+
*/
17+
export const getBaseURL = () => baseURL;
18+
19+
/**
20+
* Custom fetch wrapper that prepends the base URL
21+
* Compatible with Orval's generated RequestInit spreading
22+
*/
23+
export const customFetch = async <T>(
24+
url: string,
25+
options?: RequestInit & { params?: Record<string, unknown> },
26+
): Promise<T> => {
27+
const { params, ...requestInit } = options ?? {};
28+
29+
let targetUrl = `${baseURL}${url}`;
30+
31+
if (params) {
32+
targetUrl += "?" + new URLSearchParams(params as Record<string, string>);
33+
}
34+
35+
// If body exists and is an object, stringify it
36+
const processedBody =
37+
requestInit.body && typeof requestInit.body === "object"
38+
? JSON.stringify(requestInit.body)
39+
: requestInit.body;
40+
41+
const response = await fetch(targetUrl, {
42+
...requestInit,
43+
body: processedBody,
44+
headers: { "Content-Type": "application/json", ...requestInit.headers },
45+
});
46+
47+
if (!response.ok) {
48+
throw response;
49+
}
50+
51+
// Handle empty responses
52+
if ([204, 205, 304].includes(response.status)) {
53+
return {} as T;
54+
}
55+
56+
return response.json();
57+
};
58+
59+
export type ErrorType<Error> = Error;

src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Generated by orval
22
// API_TARGET_NAME Is replaced with the api name
3-
export * from "./api/api.schemas";
3+
export * from "./api/api-schemas";
44

55
// Request instance and methods to change the baseUrl and auth token
6-
export * from "./custom-instance";
6+
export * from "./custom-axios";

tsdown.config.ts

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import { defineConfig } from "tsdown";
22

33
export default defineConfig({
4-
entry: ["src/index.ts", "src/api/*/*.ts", "src/api/*/*.zod.ts"],
4+
entry: [
5+
"src/index.ts",
6+
"src/api/*/*.ts",
7+
"src/api/*/*.fetch.ts",
8+
"src/api/*/*.zod.ts",
9+
"!src/api/api-schemas/**",
10+
],
511
dts: true,
612
format: ["cjs", "esm"],
713
target: "es2022",
@@ -23,7 +29,7 @@ export default defineConfig({
2329
if (chunks) {
2430
for (const chunk of chunks) {
2531
if (chunk.type === "chunk" && chunk.fileName) {
26-
// Match pattern: api/{moduleName}/{moduleName}.js (or .cjs) and api/{moduleName}/{moduleName}.zod.js
32+
// Match pattern: api/{moduleName}/{moduleName}.js (or .cjs) for axios client
2733
const match = chunk.fileName.match(/^api\/([^/]+)\/\1\.(js|cjs)$/);
2834
if (match && match[1]) {
2935
modules.add(match[1]);
@@ -33,9 +39,9 @@ export default defineConfig({
3339
}
3440
}
3541

36-
// For each module, create simplified export paths for both regular and zod files
42+
// For each module, create simplified export paths for axios, fetch, and zod files
3743
for (const moduleName of modules) {
38-
// Regular exports
44+
// Axios client (default)
3945
pkg[`./${moduleName}`] = {
4046
import: {
4147
types: `./dist/api/${moduleName}/${moduleName}.d.ts`,
@@ -47,7 +53,19 @@ export default defineConfig({
4753
},
4854
};
4955

50-
// Zod exports
56+
// Fetch client
57+
pkg[`./${moduleName}/fetch`] = {
58+
import: {
59+
types: `./dist/api/${moduleName}/${moduleName}.fetch.d.ts`,
60+
default: `./dist/api/${moduleName}/${moduleName}.fetch.js`,
61+
},
62+
require: {
63+
types: `./dist/api/${moduleName}/${moduleName}.fetch.d.cts`,
64+
default: `./dist/api/${moduleName}/${moduleName}.fetch.cjs`,
65+
},
66+
};
67+
68+
// Zod schemas
5169
pkg[`./${moduleName}/zod`] = {
5270
import: {
5371
types: `./dist/api/${moduleName}/${moduleName}.zod.d.ts`,

0 commit comments

Comments
 (0)