Skip to content

Commit 262798e

Browse files
authored
Merge pull request #3432 from nmokkenstorm/feat/use-mutation-hooks
feat: generate useMutation hooks for TanStack React Query plugin
2 parents 35c8233 + 304a86f commit 262798e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+14123
-2
lines changed

.changeset/grumpy-mice-ask.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@hey-api/openapi-ts": patch
3+
---
4+
5+
**plugin(@tanstack/react-query)**: support generating `useMutation` hooks

packages/openapi-ts-tests/main/test/__snapshots__/2.0.x/plugins/@tanstack/react-query/useMutation/@tanstack/react-query.gen.ts

Lines changed: 550 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// This file is auto-generated by @hey-api/openapi-ts
2+
3+
import { type ClientOptions, type Config, createClient, createConfig } from './client';
4+
import type { ClientOptions as ClientOptions2 } from './types.gen';
5+
6+
/**
7+
* The `createClientConfig()` function will be called on client initialization
8+
* and the returned object will become the client's initial configuration.
9+
*
10+
* You may want to initialize your client this way instead of calling
11+
* `setConfig()`. This is useful for example if you're using Next.js
12+
* to ensure your client always has the correct values.
13+
*/
14+
export type CreateClientConfig<T extends ClientOptions = ClientOptions2> = (override?: Config<ClientOptions & T>) => Config<Required<ClientOptions> & T>;
15+
16+
export const client = createClient(createConfig<ClientOptions2>({ baseUrl: 'http://localhost:3000/base' }));
Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
// This file is auto-generated by @hey-api/openapi-ts
2+
3+
import { createSseClient } from '../core/serverSentEvents.gen';
4+
import type { HttpMethod } from '../core/types.gen';
5+
import { getValidRequestBody } from '../core/utils.gen';
6+
import type { Client, Config, RequestOptions, ResolvedRequestOptions } from './types.gen';
7+
import {
8+
buildUrl,
9+
createConfig,
10+
createInterceptors,
11+
getParseAs,
12+
mergeConfigs,
13+
mergeHeaders,
14+
setAuthParams,
15+
} from './utils.gen';
16+
17+
type ReqInit = Omit<RequestInit, 'body' | 'headers'> & {
18+
body?: any;
19+
headers: ReturnType<typeof mergeHeaders>;
20+
};
21+
22+
export const createClient = (config: Config = {}): Client => {
23+
let _config = mergeConfigs(createConfig(), config);
24+
25+
const getConfig = (): Config => ({ ..._config });
26+
27+
const setConfig = (config: Config): Config => {
28+
_config = mergeConfigs(_config, config);
29+
return getConfig();
30+
};
31+
32+
const interceptors = createInterceptors<Request, Response, unknown, ResolvedRequestOptions>();
33+
34+
const beforeRequest = async (options: RequestOptions) => {
35+
const opts = {
36+
..._config,
37+
...options,
38+
fetch: options.fetch ?? _config.fetch ?? globalThis.fetch,
39+
headers: mergeHeaders(_config.headers, options.headers),
40+
serializedBody: undefined as string | undefined,
41+
};
42+
43+
if (opts.security) {
44+
await setAuthParams({
45+
...opts,
46+
security: opts.security,
47+
});
48+
}
49+
50+
if (opts.requestValidator) {
51+
await opts.requestValidator(opts);
52+
}
53+
54+
if (opts.body !== undefined && opts.bodySerializer) {
55+
opts.serializedBody = opts.bodySerializer(opts.body) as string | undefined;
56+
}
57+
58+
// remove Content-Type header if body is empty to avoid sending invalid requests
59+
if (opts.body === undefined || opts.serializedBody === '') {
60+
opts.headers.delete('Content-Type');
61+
}
62+
63+
const url = buildUrl(opts);
64+
65+
return { opts, url };
66+
};
67+
68+
const request: Client['request'] = async (options) => {
69+
// @ts-expect-error
70+
const { opts, url } = await beforeRequest(options);
71+
const requestInit: ReqInit = {
72+
redirect: 'follow',
73+
...opts,
74+
body: getValidRequestBody(opts),
75+
};
76+
77+
let request = new Request(url, requestInit);
78+
79+
for (const fn of interceptors.request.fns) {
80+
if (fn) {
81+
request = await fn(request, opts);
82+
}
83+
}
84+
85+
// fetch must be assigned here, otherwise it would throw the error:
86+
// TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation
87+
const _fetch = opts.fetch!;
88+
let response: Response;
89+
90+
try {
91+
response = await _fetch(request);
92+
} catch (error) {
93+
// Handle fetch exceptions (AbortError, network errors, etc.)
94+
let finalError = error;
95+
96+
for (const fn of interceptors.error.fns) {
97+
if (fn) {
98+
finalError = (await fn(error, undefined as any, request, opts)) as unknown;
99+
}
100+
}
101+
102+
finalError = finalError || ({} as unknown);
103+
104+
if (opts.throwOnError) {
105+
throw finalError;
106+
}
107+
108+
// Return error response
109+
return opts.responseStyle === 'data'
110+
? undefined
111+
: {
112+
error: finalError,
113+
request,
114+
response: undefined as any,
115+
};
116+
}
117+
118+
for (const fn of interceptors.response.fns) {
119+
if (fn) {
120+
response = await fn(response, request, opts);
121+
}
122+
}
123+
124+
const result = {
125+
request,
126+
response,
127+
};
128+
129+
if (response.ok) {
130+
const parseAs =
131+
(opts.parseAs === 'auto'
132+
? getParseAs(response.headers.get('Content-Type'))
133+
: opts.parseAs) ?? 'json';
134+
135+
if (response.status === 204 || response.headers.get('Content-Length') === '0') {
136+
let emptyData: any;
137+
switch (parseAs) {
138+
case 'arrayBuffer':
139+
case 'blob':
140+
case 'text':
141+
emptyData = await response[parseAs]();
142+
break;
143+
case 'formData':
144+
emptyData = new FormData();
145+
break;
146+
case 'stream':
147+
emptyData = response.body;
148+
break;
149+
case 'json':
150+
default:
151+
emptyData = {};
152+
break;
153+
}
154+
return opts.responseStyle === 'data'
155+
? emptyData
156+
: {
157+
data: emptyData,
158+
...result,
159+
};
160+
}
161+
162+
let data: any;
163+
switch (parseAs) {
164+
case 'arrayBuffer':
165+
case 'blob':
166+
case 'formData':
167+
case 'text':
168+
data = await response[parseAs]();
169+
break;
170+
case 'json': {
171+
// Some servers return 200 with no Content-Length and empty body.
172+
// response.json() would throw; read as text and parse if non-empty.
173+
const text = await response.text();
174+
data = text ? JSON.parse(text) : {};
175+
break;
176+
}
177+
case 'stream':
178+
return opts.responseStyle === 'data'
179+
? response.body
180+
: {
181+
data: response.body,
182+
...result,
183+
};
184+
}
185+
186+
if (parseAs === 'json') {
187+
if (opts.responseValidator) {
188+
await opts.responseValidator(data);
189+
}
190+
191+
if (opts.responseTransformer) {
192+
data = await opts.responseTransformer(data);
193+
}
194+
}
195+
196+
return opts.responseStyle === 'data'
197+
? data
198+
: {
199+
data,
200+
...result,
201+
};
202+
}
203+
204+
const textError = await response.text();
205+
let jsonError: unknown;
206+
207+
try {
208+
jsonError = JSON.parse(textError);
209+
} catch {
210+
// noop
211+
}
212+
213+
const error = jsonError ?? textError;
214+
let finalError = error;
215+
216+
for (const fn of interceptors.error.fns) {
217+
if (fn) {
218+
finalError = (await fn(error, response, request, opts)) as string;
219+
}
220+
}
221+
222+
finalError = finalError || ({} as string);
223+
224+
if (opts.throwOnError) {
225+
throw finalError;
226+
}
227+
228+
// TODO: we probably want to return error and improve types
229+
return opts.responseStyle === 'data'
230+
? undefined
231+
: {
232+
error: finalError,
233+
...result,
234+
};
235+
};
236+
237+
const makeMethodFn = (method: Uppercase<HttpMethod>) => (options: RequestOptions) =>
238+
request({ ...options, method });
239+
240+
const makeSseFn = (method: Uppercase<HttpMethod>) => async (options: RequestOptions) => {
241+
const { opts, url } = await beforeRequest(options);
242+
return createSseClient({
243+
...opts,
244+
body: opts.body as BodyInit | null | undefined,
245+
headers: opts.headers as unknown as Record<string, string>,
246+
method,
247+
onRequest: async (url, init) => {
248+
let request = new Request(url, init);
249+
for (const fn of interceptors.request.fns) {
250+
if (fn) {
251+
request = await fn(request, opts);
252+
}
253+
}
254+
return request;
255+
},
256+
serializedBody: getValidRequestBody(opts) as BodyInit | null | undefined,
257+
url,
258+
});
259+
};
260+
261+
return {
262+
buildUrl,
263+
connect: makeMethodFn('CONNECT'),
264+
delete: makeMethodFn('DELETE'),
265+
get: makeMethodFn('GET'),
266+
getConfig,
267+
head: makeMethodFn('HEAD'),
268+
interceptors,
269+
options: makeMethodFn('OPTIONS'),
270+
patch: makeMethodFn('PATCH'),
271+
post: makeMethodFn('POST'),
272+
put: makeMethodFn('PUT'),
273+
request,
274+
setConfig,
275+
sse: {
276+
connect: makeSseFn('CONNECT'),
277+
delete: makeSseFn('DELETE'),
278+
get: makeSseFn('GET'),
279+
head: makeSseFn('HEAD'),
280+
options: makeSseFn('OPTIONS'),
281+
patch: makeSseFn('PATCH'),
282+
post: makeSseFn('POST'),
283+
put: makeSseFn('PUT'),
284+
trace: makeSseFn('TRACE'),
285+
},
286+
trace: makeMethodFn('TRACE'),
287+
} as Client;
288+
};
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// This file is auto-generated by @hey-api/openapi-ts
2+
3+
export type { Auth } from '../core/auth.gen';
4+
export type { QuerySerializerOptions } from '../core/bodySerializer.gen';
5+
export {
6+
formDataBodySerializer,
7+
jsonBodySerializer,
8+
urlSearchParamsBodySerializer,
9+
} from '../core/bodySerializer.gen';
10+
export { buildClientParams } from '../core/params.gen';
11+
export { serializeQueryKeyValue } from '../core/queryKeySerializer.gen';
12+
export { createClient } from './client.gen';
13+
export type {
14+
Client,
15+
ClientOptions,
16+
Config,
17+
CreateClientConfig,
18+
Options,
19+
RequestOptions,
20+
RequestResult,
21+
ResolvedRequestOptions,
22+
ResponseStyle,
23+
TDataShape,
24+
} from './types.gen';
25+
export { createConfig, mergeHeaders } from './utils.gen';

0 commit comments

Comments
 (0)