Skip to content

Commit f97ee82

Browse files
committed
fix(client-core): resolve runtimeConfigPath relative to generated output
Paths starting with './' or absolute paths are now resolved from the current working directory and made relative to the generated output directory, so the resulting import path is correct regardless of where the user's runtime config file lives. Paths without a './' prefix (e.g. '../my-config', 'my-config', '@scope/pkg') are kept as-is to preserve the existing convention of treating them as relative to the generated output or as bare module specifiers. Closes #3394
1 parent 5e1eaea commit f97ee82

116 files changed

Lines changed: 29859 additions & 1 deletion

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
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(@hey-api/client-core)**: resolve `runtimeConfigPath` relative to the generated output so imports are correct regardless of where the user's config file lives
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const createClientConfig = <T>(config: T): T => config;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// This file is auto-generated by @hey-api/openapi-ts
2+
3+
import { createClientConfig } from '../../../../../../../runtime-config/my-config.ts';
4+
5+
import { type ClientOptions, type Config, createClient, createConfig } from './client';
6+
import type { ClientOptions as ClientOptions2 } from './types.gen';
7+
8+
/**
9+
* The `createClientConfig()` function will be called on client initialization
10+
* and the returned object will become the client's initial configuration.
11+
*
12+
* You may want to initialize your client this way instead of calling
13+
* `setConfig()`. This is useful for example if you're using Next.js
14+
* to ensure your client always has the correct values.
15+
*/
16+
export type CreateClientConfig<T extends ClientOptions = ClientOptions2> = (override?: Config<ClientOptions & T>) => Config<Required<ClientOptions> & T>;
17+
18+
export const client = createClient(createClientConfig(createConfig<ClientOptions2>({ baseUrl: 'http://localhost:3000/base' })));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
// This file is auto-generated by @hey-api/openapi-ts
2+
3+
import type { HttpResponse } from '@angular/common/http';
4+
import { HttpClient, HttpErrorResponse, HttpEventType, HttpRequest } from '@angular/common/http';
5+
import {
6+
assertInInjectionContext,
7+
inject,
8+
provideAppInitializer,
9+
runInInjectionContext,
10+
} from '@angular/core';
11+
import { firstValueFrom } from 'rxjs';
12+
import { filter } from 'rxjs/operators';
13+
14+
import { createSseClient } from '../core/serverSentEvents.gen';
15+
import type { HttpMethod } from '../core/types.gen';
16+
import { getValidRequestBody } from '../core/utils.gen';
17+
import type {
18+
Client,
19+
Config,
20+
RequestOptions,
21+
ResolvedRequestOptions,
22+
ResponseStyle,
23+
} from './types.gen';
24+
import {
25+
buildUrl,
26+
createConfig,
27+
createInterceptors,
28+
mergeConfigs,
29+
mergeHeaders,
30+
setAuthParams,
31+
} from './utils.gen';
32+
33+
export function provideHeyApiClient(client: Client) {
34+
return provideAppInitializer(() => {
35+
const httpClient = inject(HttpClient);
36+
client.setConfig({ httpClient });
37+
});
38+
}
39+
40+
export const createClient = (config: Config = {}): Client => {
41+
let _config = mergeConfigs(createConfig(), config);
42+
43+
const getConfig = (): Config => ({ ..._config });
44+
45+
const setConfig = (config: Config): Config => {
46+
_config = mergeConfigs(_config, config);
47+
return getConfig();
48+
};
49+
50+
const interceptors = createInterceptors<
51+
HttpRequest<unknown>,
52+
HttpResponse<unknown>,
53+
unknown,
54+
ResolvedRequestOptions
55+
>();
56+
57+
const requestOptions = <
58+
TData = unknown,
59+
ThrowOnError extends boolean = false,
60+
TResponseStyle extends ResponseStyle = 'fields',
61+
>(
62+
options: RequestOptions<TData, TResponseStyle, ThrowOnError>,
63+
) => {
64+
const opts = {
65+
..._config,
66+
...options,
67+
headers: mergeHeaders(_config.headers, options.headers),
68+
httpClient: options.httpClient ?? _config.httpClient,
69+
serializedBody: undefined as string | undefined,
70+
};
71+
72+
if (!opts.httpClient) {
73+
if (opts.injector) {
74+
opts.httpClient = runInInjectionContext(opts.injector, () => inject(HttpClient));
75+
} else {
76+
assertInInjectionContext(requestOptions);
77+
opts.httpClient = inject(HttpClient);
78+
}
79+
}
80+
81+
if (opts.body !== undefined && opts.bodySerializer) {
82+
opts.serializedBody = opts.bodySerializer(opts.body) as string | undefined;
83+
}
84+
85+
// remove Content-Type header if body is empty to avoid sending invalid requests
86+
if (opts.body === undefined || opts.serializedBody === '') {
87+
opts.headers.delete('Content-Type');
88+
}
89+
90+
const url = buildUrl(opts as any);
91+
92+
const req = new HttpRequest<unknown>(opts.method ?? 'GET', url, getValidRequestBody(opts), {
93+
redirect: 'follow',
94+
...opts,
95+
});
96+
97+
return { opts, req, url };
98+
};
99+
100+
const beforeRequest = async <
101+
TData = unknown,
102+
TResponseStyle extends ResponseStyle = 'fields',
103+
ThrowOnError extends boolean = boolean,
104+
Url extends string = string,
105+
>(
106+
options: RequestOptions<TData, TResponseStyle, ThrowOnError, Url>,
107+
) => {
108+
const { opts, req, url } = requestOptions(options);
109+
110+
if (opts.security) {
111+
await setAuthParams({
112+
...opts,
113+
security: opts.security,
114+
});
115+
}
116+
117+
if (opts.requestValidator) {
118+
await opts.requestValidator(opts);
119+
}
120+
121+
return { opts, req, url };
122+
};
123+
124+
const request: Client['request'] = async (options) => {
125+
const { opts, req: initialReq } = await beforeRequest(options);
126+
127+
let req = initialReq;
128+
129+
for (const fn of interceptors.request.fns) {
130+
if (fn) {
131+
req = await fn(req, opts as any);
132+
}
133+
}
134+
135+
const result: {
136+
request: HttpRequest<unknown>;
137+
response: any;
138+
} = {
139+
request: req,
140+
response: null,
141+
};
142+
143+
try {
144+
result.response = (await firstValueFrom(
145+
opts
146+
.httpClient!.request(req)
147+
.pipe(filter((event) => event.type === HttpEventType.Response)),
148+
)) as HttpResponse<unknown>;
149+
150+
for (const fn of interceptors.response.fns) {
151+
if (fn) {
152+
result.response = await fn(result.response, req, opts as any);
153+
}
154+
}
155+
156+
let bodyResponse = result.response.body;
157+
158+
if (opts.responseValidator) {
159+
await opts.responseValidator(bodyResponse);
160+
}
161+
162+
if (opts.responseTransformer) {
163+
bodyResponse = await opts.responseTransformer(bodyResponse);
164+
}
165+
166+
return opts.responseStyle === 'data' ? bodyResponse : { data: bodyResponse, ...result };
167+
} catch (error) {
168+
if (error instanceof HttpErrorResponse) {
169+
result.response = error;
170+
}
171+
172+
let finalError = error instanceof HttpErrorResponse ? error.error : error;
173+
174+
for (const fn of interceptors.error.fns) {
175+
if (fn) {
176+
finalError = (await fn(finalError, result.response as any, req, opts as any)) as string;
177+
}
178+
}
179+
180+
if (opts.throwOnError) {
181+
throw finalError;
182+
}
183+
184+
return opts.responseStyle === 'data'
185+
? undefined
186+
: {
187+
error: finalError,
188+
...result,
189+
};
190+
}
191+
};
192+
193+
const makeMethodFn = (method: Uppercase<HttpMethod>) => (options: RequestOptions) =>
194+
request({ ...options, method });
195+
196+
const makeSseFn = (method: Uppercase<HttpMethod>) => async (options: RequestOptions) => {
197+
const { opts, url } = await beforeRequest(options);
198+
return createSseClient({
199+
...opts,
200+
body: opts.body as BodyInit | null | undefined,
201+
headers: opts.headers as unknown as Record<string, string>,
202+
method,
203+
serializedBody: getValidRequestBody(opts) as BodyInit | null | undefined,
204+
url,
205+
});
206+
};
207+
208+
const _buildUrl: Client['buildUrl'] = (options) => buildUrl({ ..._config, ...options });
209+
210+
return {
211+
buildUrl: _buildUrl,
212+
connect: makeMethodFn('CONNECT'),
213+
delete: makeMethodFn('DELETE'),
214+
get: makeMethodFn('GET'),
215+
getConfig,
216+
head: makeMethodFn('HEAD'),
217+
interceptors,
218+
options: makeMethodFn('OPTIONS'),
219+
patch: makeMethodFn('PATCH'),
220+
post: makeMethodFn('POST'),
221+
put: makeMethodFn('PUT'),
222+
request,
223+
requestOptions: (options) => {
224+
if (options.security) {
225+
throw new Error('Security is not supported in requestOptions');
226+
}
227+
228+
if (options.requestValidator) {
229+
throw new Error('Request validation is not supported in requestOptions');
230+
}
231+
232+
return requestOptions(options).req;
233+
},
234+
setConfig,
235+
sse: {
236+
connect: makeSseFn('CONNECT'),
237+
delete: makeSseFn('DELETE'),
238+
get: makeSseFn('GET'),
239+
head: makeSseFn('HEAD'),
240+
options: makeSseFn('OPTIONS'),
241+
patch: makeSseFn('PATCH'),
242+
post: makeSseFn('POST'),
243+
put: makeSseFn('PUT'),
244+
trace: makeSseFn('TRACE'),
245+
},
246+
trace: makeMethodFn('TRACE'),
247+
} as Client;
248+
};
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)