@hey-api/openapi-ts 0.80.10 → 0.80.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,14 +1,26 @@
1
1
  import type { HttpResponse } from '@angular/common/http';
2
- import { HttpClient, HttpEventType, HttpRequest } from '@angular/common/http';
2
+ import {
3
+ HttpClient,
4
+ HttpErrorResponse,
5
+ HttpEventType,
6
+ HttpRequest,
7
+ } from '@angular/common/http';
3
8
  import {
4
9
  assertInInjectionContext,
5
10
  inject,
6
11
  provideAppInitializer,
12
+ runInInjectionContext,
7
13
  } from '@angular/core';
8
14
  import { firstValueFrom } from 'rxjs';
9
15
  import { filter } from 'rxjs/operators';
10
16
 
11
- import type { Client, Config, ResolvedRequestOptions } from './types';
17
+ import type {
18
+ Client,
19
+ Config,
20
+ RequestOptions,
21
+ ResolvedRequestOptions,
22
+ ResponseStyle,
23
+ } from './types';
12
24
  import {
13
25
  buildUrl,
14
26
  createConfig,
@@ -42,20 +54,59 @@ export const createClient = (config: Config = {}): Client => {
42
54
  ResolvedRequestOptions
43
55
  >();
44
56
 
45
- const request: Client['request'] = async (options) => {
57
+ const requestOptions = <
58
+ ThrowOnError extends boolean = false,
59
+ TResponseStyle extends ResponseStyle = 'fields',
60
+ >(
61
+ options: RequestOptions<TResponseStyle, ThrowOnError>,
62
+ ) => {
46
63
  const opts = {
47
64
  ..._config,
48
65
  ...options,
49
66
  headers: mergeHeaders(_config.headers, options.headers),
50
67
  httpClient: options.httpClient ?? _config.httpClient,
51
- serializedBody: undefined,
68
+ method: 'GET',
69
+ serializedBody: options.body as any,
52
70
  };
53
71
 
54
72
  if (!opts.httpClient) {
55
- assertInInjectionContext(request);
56
- opts.httpClient = inject(HttpClient);
73
+ if (opts.injector) {
74
+ opts.httpClient = runInInjectionContext(opts.injector, () =>
75
+ inject(HttpClient),
76
+ );
77
+ } else {
78
+ assertInInjectionContext(requestOptions);
79
+ opts.httpClient = inject(HttpClient);
80
+ }
81
+ }
82
+
83
+ if (opts.body && opts.bodySerializer) {
84
+ opts.serializedBody = opts.bodySerializer(opts.body);
85
+ }
86
+
87
+ // remove Content-Type header if body is empty to avoid sending invalid requests
88
+ if (opts.serializedBody === undefined || opts.serializedBody === '') {
89
+ opts.headers.delete('Content-Type');
57
90
  }
58
91
 
92
+ const url = buildUrl(opts as any);
93
+
94
+ const req = new HttpRequest<unknown>(
95
+ opts.method,
96
+ url,
97
+ opts.serializedBody || null,
98
+ {
99
+ redirect: 'follow',
100
+ ...opts,
101
+ },
102
+ );
103
+
104
+ return { opts, req };
105
+ };
106
+
107
+ const request: Client['request'] = async (options) => {
108
+ const { opts, req: initialReq } = requestOptions(options);
109
+
59
110
  if (opts.security) {
60
111
  await setAuthParams({
61
112
  ...opts,
@@ -67,26 +118,11 @@ export const createClient = (config: Config = {}): Client => {
67
118
  await opts.requestValidator(opts);
68
119
  }
69
120
 
70
- if (opts.body && opts.bodySerializer) {
71
- opts.serializedBody = opts.bodySerializer(opts.body);
72
- }
73
-
74
- // remove Content-Type header if body is empty to avoid sending invalid requests
75
- if (opts.serializedBody === undefined || opts.serializedBody === '') {
76
- opts.headers.delete('Content-Type');
77
- }
78
-
79
- const url = buildUrl(opts);
80
-
81
- let req = new HttpRequest<unknown>(opts.method, url, {
82
- redirect: 'follow',
83
- ...opts,
84
- body: opts.serializedBody,
85
- });
121
+ let req = initialReq;
86
122
 
87
123
  for (const fn of interceptors.request._fns) {
88
124
  if (fn) {
89
- req = await fn(req, opts);
125
+ req = await fn(req, opts as any);
90
126
  }
91
127
  }
92
128
 
@@ -98,46 +134,56 @@ export const createClient = (config: Config = {}): Client => {
98
134
 
99
135
  try {
100
136
  response = await firstValueFrom(
101
- opts.httpClient
102
- .request(req)
137
+ opts
138
+ .httpClient!.request(req)
103
139
  .pipe(filter((event) => event.type === HttpEventType.Response)),
104
140
  );
105
141
 
106
142
  for (const fn of interceptors.response._fns) {
107
143
  if (fn) {
108
- response = await fn(response, req, opts);
144
+ response = await fn(response, req, opts as any);
109
145
  }
110
146
  }
111
147
 
112
- let bodyResponse = response.body as Record<string, unknown>;
148
+ let bodyResponse: any = response.body;
113
149
 
114
150
  if (opts.responseValidator) {
115
151
  await opts.responseValidator(bodyResponse);
116
152
  }
117
153
 
118
154
  if (opts.responseTransformer) {
119
- bodyResponse = (await opts.responseTransformer(bodyResponse)) as Record<
120
- string,
121
- unknown
122
- >;
155
+ bodyResponse = await opts.responseTransformer(bodyResponse);
123
156
  }
124
157
 
125
- return (
126
- opts.responseStyle === 'data'
127
- ? bodyResponse
128
- : { data: bodyResponse, ...result }
129
- ) as any;
158
+ return opts.responseStyle === 'data'
159
+ ? bodyResponse
160
+ : { data: bodyResponse, ...result };
130
161
  } catch (error) {
162
+ if (error instanceof HttpErrorResponse) {
163
+ response = error;
164
+ }
165
+
166
+ let finalError = error instanceof HttpErrorResponse ? error.error : error;
167
+
131
168
  for (const fn of interceptors.error._fns) {
132
169
  if (fn) {
133
- (await fn(error, response!, req, opts)) as string;
170
+ finalError = (await fn(
171
+ finalError,
172
+ response as HttpResponse<unknown>,
173
+ req,
174
+ opts as any,
175
+ )) as string;
134
176
  }
135
177
  }
136
178
 
179
+ if (opts.throwOnError) {
180
+ throw finalError;
181
+ }
182
+
137
183
  return opts.responseStyle === 'data'
138
184
  ? undefined
139
185
  : {
140
- error,
186
+ error: finalError,
141
187
  ...result,
142
188
  };
143
189
  }
@@ -156,6 +202,19 @@ export const createClient = (config: Config = {}): Client => {
156
202
  post: (options) => request({ ...options, method: 'POST' }),
157
203
  put: (options) => request({ ...options, method: 'PUT' }),
158
204
  request,
205
+ requestOptions: (options) => {
206
+ if (options.security) {
207
+ throw new Error('Security is not supported in requestOptions');
208
+ }
209
+
210
+ if (options.requestValidator) {
211
+ throw new Error(
212
+ 'Request validation is not supported in requestOptions',
213
+ );
214
+ }
215
+
216
+ return requestOptions(options).req;
217
+ },
159
218
  setConfig,
160
219
  trace: (options) => request({ ...options, method: 'TRACE' }),
161
220
  };
@@ -1,8 +1,11 @@
1
1
  import type {
2
2
  HttpClient,
3
+ HttpErrorResponse,
4
+ HttpHeaders,
3
5
  HttpRequest,
4
6
  HttpResponse,
5
7
  } from '@angular/common/http';
8
+ import type { Injector } from '@angular/core';
6
9
 
7
10
  import type { Auth } from '../core/auth';
8
11
  import type {
@@ -15,11 +18,29 @@ export type ResponseStyle = 'data' | 'fields';
15
18
 
16
19
  export interface Config<T extends ClientOptions = ClientOptions>
17
20
  extends Omit<RequestInit, 'body' | 'headers' | 'method'>,
18
- CoreConfig {
21
+ Omit<CoreConfig, 'headers'> {
19
22
  /**
20
23
  * Base URL for all requests made by this client.
21
24
  */
22
25
  baseUrl?: T['baseUrl'];
26
+ /**
27
+ * An object containing any HTTP headers that you want to pre-populate your
28
+ * `HttpHeaders` object with.
29
+ *
30
+ * {@link https://angular.dev/api/common/http/HttpHeaders#constructor See more}
31
+ */
32
+ headers?:
33
+ | HttpHeaders
34
+ | Record<
35
+ string,
36
+ | string
37
+ | number
38
+ | boolean
39
+ | (string | number | boolean)[]
40
+ | null
41
+ | undefined
42
+ | unknown
43
+ >;
23
44
  /**
24
45
  * The HTTP client to use for making requests.
25
46
  */
@@ -30,6 +51,13 @@ export interface Config<T extends ClientOptions = ClientOptions>
30
51
  * @default 'fields'
31
52
  */
32
53
  responseStyle?: ResponseStyle;
54
+
55
+ /**
56
+ * Throw an error instead of returning it in the response?
57
+ *
58
+ * @default false
59
+ */
60
+ throwOnError?: T['throwOnError'];
33
61
  }
34
62
 
35
63
  export interface RequestOptions<
@@ -46,6 +74,10 @@ export interface RequestOptions<
46
74
  * {@link https://developer.mozilla.org/docs/Web/API/fetch#body}
47
75
  */
48
76
  body?: unknown;
77
+ /**
78
+ * Optional custom injector for dependency resolution if you don't implicitly or explicitly provide one.
79
+ */
80
+ injector?: Injector;
49
81
  path?: Record<string, unknown>;
50
82
  query?: Record<string, unknown>;
51
83
  /**
@@ -68,45 +100,41 @@ export type RequestResult<
68
100
  TError = unknown,
69
101
  ThrowOnError extends boolean = boolean,
70
102
  TResponseStyle extends ResponseStyle = 'fields',
71
- > = ThrowOnError extends true
72
- ? Promise<
73
- TResponseStyle extends 'data'
74
- ? TData extends Record<string, unknown>
75
- ? TData[keyof TData]
76
- : TData
77
- : {
78
- data: TData extends Record<string, unknown>
79
- ? TData[keyof TData]
80
- : TData;
81
- request: Request;
82
- response: Response;
83
- }
84
- >
85
- : Promise<
86
- TResponseStyle extends 'data'
87
- ?
88
- | (TData extends Record<string, unknown>
103
+ > = Promise<
104
+ ThrowOnError extends true
105
+ ? TResponseStyle extends 'data'
106
+ ? TData extends Record<string, unknown>
107
+ ? TData[keyof TData]
108
+ : TData
109
+ : {
110
+ data: TData extends Record<string, unknown>
111
+ ? TData[keyof TData]
112
+ : TData;
113
+ request: HttpRequest<unknown>;
114
+ response: HttpResponse<TData>;
115
+ }
116
+ : TResponseStyle extends 'data'
117
+ ?
118
+ | (TData extends Record<string, unknown> ? TData[keyof TData] : TData)
119
+ | undefined
120
+ :
121
+ | {
122
+ data: TData extends Record<string, unknown>
89
123
  ? TData[keyof TData]
90
- : TData)
91
- | undefined
92
- : (
93
- | {
94
- data: TData extends Record<string, unknown>
95
- ? TData[keyof TData]
96
- : TData;
97
- error: undefined;
98
- }
99
- | {
100
- data: undefined;
101
- error: TError extends Record<string, unknown>
102
- ? TError[keyof TError]
103
- : TError;
104
- }
105
- ) & {
106
- request: Request;
107
- response: Response;
108
- }
109
- >;
124
+ : TData;
125
+ error: undefined;
126
+ request: HttpRequest<unknown>;
127
+ response: HttpResponse<TData>;
128
+ }
129
+ | {
130
+ data: undefined;
131
+ error: TError[keyof TError];
132
+ request: HttpRequest<unknown>;
133
+ response: HttpErrorResponse & {
134
+ error: TError[keyof TError] | null;
135
+ };
136
+ }
137
+ >;
110
138
 
111
139
  export interface ClientOptions {
112
140
  baseUrl?: string;
@@ -133,6 +161,13 @@ type RequestFn = <
133
161
  Pick<Required<RequestOptions<TResponseStyle, ThrowOnError>>, 'method'>,
134
162
  ) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>;
135
163
 
164
+ type RequestOptionsFn = <
165
+ ThrowOnError extends boolean = false,
166
+ TResponseStyle extends ResponseStyle = 'fields',
167
+ >(
168
+ options: RequestOptions<TResponseStyle, ThrowOnError>,
169
+ ) => HttpRequest<unknown>;
170
+
136
171
  type BuildUrlFn = <
137
172
  TData extends {
138
173
  body?: unknown;
@@ -151,6 +186,8 @@ export type Client = CoreClient<RequestFn, Config, MethodFn, BuildUrlFn> & {
151
186
  unknown,
152
187
  ResolvedRequestOptions
153
188
  >;
189
+
190
+ requestOptions: RequestOptionsFn;
154
191
  };
155
192
 
156
193
  /**
@@ -1,9 +1,10 @@
1
+ import { HttpHeaders } from '@angular/common/http';
2
+
1
3
  import { getAuthToken } from '../core/auth';
2
4
  import type {
3
5
  QuerySerializer,
4
6
  QuerySerializerOptions,
5
7
  } from '../core/bodySerializer';
6
- import { jsonBodySerializer } from '../core/bodySerializer';
7
8
  import {
8
9
  serializeArrayParam,
9
10
  serializeObjectParam,
@@ -186,14 +187,13 @@ export const getParseAs = (
186
187
  return;
187
188
  };
188
189
 
189
- export const setAuthParams = async ({
190
- security,
191
- ...options
192
- }: Pick<Required<RequestOptions>, 'security'> &
193
- Pick<RequestOptions, 'auth' | 'query'> & {
194
- headers: Headers;
195
- }) => {
196
- for (const auth of security) {
190
+ export const setAuthParams = async (
191
+ options: Pick<Required<RequestOptions>, 'security'> &
192
+ Pick<RequestOptions, 'auth' | 'query'> & {
193
+ headers: HttpHeaders;
194
+ },
195
+ ) => {
196
+ for (const auth of options.security) {
197
197
  const token = await getAuthToken(auth, options.auth);
198
198
 
199
199
  if (!token) {
@@ -210,11 +210,11 @@ export const setAuthParams = async ({
210
210
  options.query[name] = token;
211
211
  break;
212
212
  case 'cookie':
213
- options.headers.append('Cookie', `${name}=${token}`);
213
+ options.headers = options.headers.append('Cookie', `${name}=${token}`);
214
214
  break;
215
215
  case 'header':
216
216
  default:
217
- options.headers.set(name, token);
217
+ options.headers = options.headers.set(name, token);
218
218
  break;
219
219
  }
220
220
 
@@ -275,33 +275,47 @@ export const mergeConfigs = (a: Config, b: Config): Config => {
275
275
 
276
276
  export const mergeHeaders = (
277
277
  ...headers: Array<Required<Config>['headers'] | undefined>
278
- ): Headers => {
279
- const mergedHeaders = new Headers();
278
+ ): HttpHeaders => {
279
+ let mergedHeaders = new HttpHeaders();
280
+
280
281
  for (const header of headers) {
281
282
  if (!header || typeof header !== 'object') {
282
283
  continue;
283
284
  }
284
285
 
285
- const iterator =
286
- header instanceof Headers ? header.entries() : Object.entries(header);
287
-
288
- for (const [key, value] of iterator) {
289
- if (value === null) {
290
- mergedHeaders.delete(key);
291
- } else if (Array.isArray(value)) {
292
- for (const v of value) {
293
- mergedHeaders.append(key, v as string);
286
+ if (header instanceof HttpHeaders) {
287
+ // Merge HttpHeaders instance
288
+ header.keys().forEach((key) => {
289
+ const values = header.getAll(key);
290
+ if (values) {
291
+ values.forEach((value) => {
292
+ mergedHeaders = mergedHeaders.append(key, value);
293
+ });
294
+ }
295
+ });
296
+ } else {
297
+ // Merge plain object headers
298
+ for (const [key, value] of Object.entries(header)) {
299
+ if (value === null) {
300
+ mergedHeaders = mergedHeaders.delete(key);
301
+ } else if (Array.isArray(value)) {
302
+ for (const v of value) {
303
+ mergedHeaders = mergedHeaders.append(key, v as string);
304
+ }
305
+ } else if (value !== undefined) {
306
+ // assume object headers are meant to be JSON stringified, i.e. their
307
+ // content value in OpenAPI specification is 'application/json'
308
+ mergedHeaders = mergedHeaders.set(
309
+ key,
310
+ typeof value === 'object'
311
+ ? JSON.stringify(value)
312
+ : (value as string),
313
+ );
294
314
  }
295
- } else if (value !== undefined) {
296
- // assume object headers are meant to be JSON stringified, i.e. their
297
- // content value in OpenAPI specification is 'application/json'
298
- mergedHeaders.set(
299
- key,
300
- typeof value === 'object' ? JSON.stringify(value) : (value as string),
301
- );
302
315
  }
303
316
  }
304
317
  }
318
+
305
319
  return mergedHeaders;
306
320
  };
307
321
 
@@ -409,9 +423,7 @@ const defaultHeaders = {
409
423
  export const createConfig = <T extends ClientOptions = ClientOptions>(
410
424
  override: Config<Omit<ClientOptions, keyof T> & T> = {},
411
425
  ): Config<Omit<ClientOptions, keyof T> & T> => ({
412
- ...jsonBodySerializer,
413
426
  headers: defaultHeaders,
414
- // parseAs: 'auto',
415
427
  querySerializer: defaultQuerySerializer,
416
428
  ...override,
417
429
  });