@hey-api/openapi-ts 0.96.1 → 0.97.1

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.
package/README.md CHANGED
@@ -55,7 +55,7 @@ Start with our [Contributing](https://heyapi.dev/openapi-ts/community/contributi
55
55
 
56
56
  ## Sponsors
57
57
 
58
- Hey API is sponsor-funded. If you rely on Hey API in production, consider becoming a [sponsor](https://github.com/sponsors/hey-api) to accelerate the roadmap.
58
+ Partners behind the future of API tooling. [Become a sponsor](https://github.com/sponsors/hey-api).
59
59
 
60
60
  <h3 align="center">Gold</h3>
61
61
 
@@ -85,7 +85,7 @@ export const createClient = (config: Config = {}): Client => {
85
85
  opts.headers.delete('Content-Type');
86
86
  }
87
87
 
88
- const url = buildUrl(opts as any);
88
+ const url = buildUrl(opts as Config & RequestOptions);
89
89
 
90
90
  const req = new HttpRequest<unknown>(opts.method ?? 'GET', url, getValidRequestBody(opts), {
91
91
  redirect: 'follow',
@@ -120,34 +120,39 @@ export const createClient = (config: Config = {}): Client => {
120
120
  };
121
121
 
122
122
  const request: Client['request'] = async (options) => {
123
- const { opts, req: initialReq } = await beforeRequest(options);
124
-
125
- let req = initialReq;
126
-
127
- for (const fn of interceptors.request.fns) {
128
- if (fn) {
129
- req = await fn(req, opts as any);
130
- }
131
- }
123
+ const throwOnError = options.throwOnError ?? _config.throwOnError;
124
+ const responseStyle = options.responseStyle ?? _config.responseStyle;
132
125
 
133
126
  const result: {
134
- request: HttpRequest<unknown>;
135
- response: any;
127
+ request?: HttpRequest<unknown>;
128
+ response?: any;
136
129
  } = {
137
- request: req,
138
- response: null,
130
+ request: undefined,
131
+ response: undefined,
139
132
  };
140
133
 
141
134
  try {
142
- result.response = (await firstValueFrom(
135
+ const { opts, req: initialReq } = await beforeRequest(options);
136
+
137
+ let req = initialReq;
138
+ result.request = req;
139
+
140
+ for (const fn of interceptors.request.fns) {
141
+ if (fn) {
142
+ req = await fn(req, opts as ResolvedRequestOptions);
143
+ result.request = req;
144
+ }
145
+ }
146
+
147
+ result.response = await firstValueFrom(
143
148
  opts
144
149
  .httpClient!.request(req)
145
150
  .pipe(filter((event) => event.type === HttpEventType.Response)),
146
- )) as HttpResponse<unknown>;
151
+ );
147
152
 
148
153
  for (const fn of interceptors.response.fns) {
149
154
  if (fn) {
150
- result.response = await fn(result.response, req, opts as any);
155
+ result.response = await fn(result.response, req, opts as ResolvedRequestOptions);
151
156
  }
152
157
  }
153
158
 
@@ -171,15 +176,20 @@ export const createClient = (config: Config = {}): Client => {
171
176
 
172
177
  for (const fn of interceptors.error.fns) {
173
178
  if (fn) {
174
- finalError = (await fn(finalError, result.response as any, req, opts as any)) as string;
179
+ finalError = await fn(
180
+ finalError,
181
+ result.response,
182
+ result.request,
183
+ options as ResolvedRequestOptions,
184
+ );
175
185
  }
176
186
  }
177
187
 
178
- if (opts.throwOnError) {
188
+ if (throwOnError) {
179
189
  throw finalError;
180
190
  }
181
191
 
182
- return opts.responseStyle === 'data'
192
+ return responseStyle === 'data'
183
193
  ? undefined
184
194
  : {
185
195
  error: finalError,
@@ -130,8 +130,10 @@ export type RequestResult<
130
130
  | {
131
131
  data: undefined;
132
132
  error: TError[keyof TError];
133
- request: HttpRequest<unknown>;
134
- response: HttpErrorResponse & {
133
+ /** request may be undefined, because error may be from building the request object itself */
134
+ request?: HttpRequest<unknown>;
135
+ /** response may be undefined, because error may be from building the request object itself or from a network error */
136
+ response?: HttpErrorResponse & {
135
137
  error: TError[keyof TError] | null;
136
138
  };
137
139
  }
@@ -312,8 +312,10 @@ export const mergeHeaders = (
312
312
 
313
313
  type ErrInterceptor<Err, Res, Req, Options> = (
314
314
  error: Err,
315
- response: Res,
316
- request: Req,
315
+ /** response may be undefined due to a network error where no response object is produced */
316
+ response: Res | undefined,
317
+ /** request may be undefined, because error may be from building the request object itself */
318
+ request: Req | undefined,
317
319
  options: Options,
318
320
  ) => Err | Promise<Err>;
319
321
 
@@ -73,171 +73,154 @@ export const createClient = (config: Config = {}): Client => {
73
73
  };
74
74
 
75
75
  const request: Client['request'] = async (options) => {
76
- const { opts, url } = await beforeRequest(options);
77
- const requestInit: ReqInit = {
78
- redirect: 'follow',
79
- ...opts,
80
- body: getValidRequestBody(opts),
81
- };
82
-
83
- let request = new Request(url, requestInit);
84
-
85
- for (const fn of interceptors.request.fns) {
86
- if (fn) {
87
- request = await fn(request, opts);
88
- }
89
- }
76
+ const throwOnError = options.throwOnError ?? _config.throwOnError;
77
+ const responseStyle = options.responseStyle ?? _config.responseStyle;
90
78
 
91
- // fetch must be assigned here, otherwise it would throw the error:
92
- // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation
93
- const _fetch = opts.fetch!;
94
- let response: Response;
79
+ let request: Request | undefined;
80
+ let response: Response | undefined;
95
81
 
96
82
  try {
97
- response = await _fetch(request);
98
- } catch (error) {
99
- // Handle fetch exceptions (AbortError, network errors, etc.)
100
- let finalError = error;
83
+ const { opts, url } = await beforeRequest(options);
84
+ const requestInit: ReqInit = {
85
+ redirect: 'follow',
86
+ ...opts,
87
+ body: getValidRequestBody(opts),
88
+ };
101
89
 
102
- for (const fn of interceptors.error.fns) {
90
+ request = new Request(url, requestInit);
91
+
92
+ for (const fn of interceptors.request.fns) {
103
93
  if (fn) {
104
- finalError = (await fn(error, undefined as any, request, opts)) as unknown;
94
+ request = await fn(request, opts);
105
95
  }
106
96
  }
107
97
 
108
- finalError = finalError || ({} as unknown);
98
+ // fetch must be assigned here, otherwise it would throw the error:
99
+ // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation
100
+ const _fetch = opts.fetch!;
109
101
 
110
- if (opts.throwOnError) {
111
- throw finalError;
112
- }
113
-
114
- // Return error response
115
- return opts.responseStyle === 'data'
116
- ? undefined
117
- : {
118
- error: finalError,
119
- request,
120
- response: undefined as any,
121
- };
122
- }
102
+ response = await _fetch(request);
123
103
 
124
- for (const fn of interceptors.response.fns) {
125
- if (fn) {
126
- response = await fn(response, request, opts);
104
+ for (const fn of interceptors.response.fns) {
105
+ if (fn) {
106
+ response = await fn(response, request, opts);
107
+ }
127
108
  }
128
- }
129
-
130
- const result = {
131
- request,
132
- response,
133
- };
134
109
 
135
- if (response.ok) {
136
- const parseAs =
137
- (opts.parseAs === 'auto'
138
- ? getParseAs(response.headers.get('Content-Type'))
139
- : opts.parseAs) ?? 'json';
110
+ const result = {
111
+ request,
112
+ response,
113
+ };
114
+
115
+ if (response.ok) {
116
+ const parseAs =
117
+ (opts.parseAs === 'auto'
118
+ ? getParseAs(response.headers.get('Content-Type'))
119
+ : opts.parseAs) ?? 'json';
120
+
121
+ if (response.status === 204 || response.headers.get('Content-Length') === '0') {
122
+ let emptyData: any;
123
+ switch (parseAs) {
124
+ case 'arrayBuffer':
125
+ case 'blob':
126
+ case 'text':
127
+ emptyData = await response[parseAs]();
128
+ break;
129
+ case 'formData':
130
+ emptyData = new FormData();
131
+ break;
132
+ case 'stream':
133
+ emptyData = response.body;
134
+ break;
135
+ case 'json':
136
+ default:
137
+ emptyData = {};
138
+ break;
139
+ }
140
+ return opts.responseStyle === 'data'
141
+ ? emptyData
142
+ : {
143
+ data: emptyData,
144
+ ...result,
145
+ };
146
+ }
140
147
 
141
- if (response.status === 204 || response.headers.get('Content-Length') === '0') {
142
- let emptyData: any;
148
+ let data: any;
143
149
  switch (parseAs) {
144
150
  case 'arrayBuffer':
145
151
  case 'blob':
152
+ case 'formData':
146
153
  case 'text':
147
- emptyData = await response[parseAs]();
154
+ data = await response[parseAs]();
148
155
  break;
149
- case 'formData':
150
- emptyData = new FormData();
156
+ case 'json': {
157
+ // Some servers return 200 with no Content-Length and empty body.
158
+ // response.json() would throw; read as text and parse if non-empty.
159
+ const text = await response.text();
160
+ data = text ? JSON.parse(text) : {};
151
161
  break;
162
+ }
152
163
  case 'stream':
153
- emptyData = response.body;
154
- break;
155
- case 'json':
156
- default:
157
- emptyData = {};
158
- break;
164
+ return opts.responseStyle === 'data'
165
+ ? response.body
166
+ : {
167
+ data: response.body,
168
+ ...result,
169
+ };
159
170
  }
171
+
172
+ if (parseAs === 'json') {
173
+ if (opts.responseValidator) {
174
+ await opts.responseValidator(data);
175
+ }
176
+
177
+ if (opts.responseTransformer) {
178
+ data = await opts.responseTransformer(data);
179
+ }
180
+ }
181
+
160
182
  return opts.responseStyle === 'data'
161
- ? emptyData
183
+ ? data
162
184
  : {
163
- data: emptyData,
185
+ data,
164
186
  ...result,
165
187
  };
166
188
  }
167
189
 
168
- let data: any;
169
- switch (parseAs) {
170
- case 'arrayBuffer':
171
- case 'blob':
172
- case 'formData':
173
- case 'text':
174
- data = await response[parseAs]();
175
- break;
176
- case 'json': {
177
- // Some servers return 200 with no Content-Length and empty body.
178
- // response.json() would throw; read as text and parse if non-empty.
179
- const text = await response.text();
180
- data = text ? JSON.parse(text) : {};
181
- break;
182
- }
183
- case 'stream':
184
- return opts.responseStyle === 'data'
185
- ? response.body
186
- : {
187
- data: response.body,
188
- ...result,
189
- };
190
+ const textError = await response.text();
191
+ let jsonError: unknown;
192
+
193
+ try {
194
+ jsonError = JSON.parse(textError);
195
+ } catch {
196
+ // noop
190
197
  }
191
198
 
192
- if (parseAs === 'json') {
193
- if (opts.responseValidator) {
194
- await opts.responseValidator(data);
195
- }
199
+ throw jsonError ?? textError;
200
+ } catch (error) {
201
+ let finalError = error;
196
202
 
197
- if (opts.responseTransformer) {
198
- data = await opts.responseTransformer(data);
203
+ for (const fn of interceptors.error.fns) {
204
+ if (fn) {
205
+ finalError = await fn(finalError, response, request, options as ResolvedRequestOptions);
199
206
  }
200
207
  }
201
208
 
202
- return opts.responseStyle === 'data'
203
- ? data
204
- : {
205
- data,
206
- ...result,
207
- };
208
- }
209
-
210
- const textError = await response.text();
211
- let jsonError: unknown;
212
-
213
- try {
214
- jsonError = JSON.parse(textError);
215
- } catch {
216
- // noop
217
- }
218
-
219
- const error = jsonError ?? textError;
220
- let finalError = error;
209
+ finalError = finalError || {};
221
210
 
222
- for (const fn of interceptors.error.fns) {
223
- if (fn) {
224
- finalError = (await fn(error, response, request, opts)) as string;
211
+ if (throwOnError) {
212
+ throw finalError;
225
213
  }
226
- }
227
-
228
- finalError = finalError || ({} as string);
229
214
 
230
- if (opts.throwOnError) {
231
- throw finalError;
215
+ // TODO: we probably want to return error and improve types
216
+ return responseStyle === 'data'
217
+ ? undefined
218
+ : {
219
+ error: finalError,
220
+ request,
221
+ response,
222
+ };
232
223
  }
233
-
234
- // TODO: we probably want to return error and improve types
235
- return opts.responseStyle === 'data'
236
- ? undefined
237
- : {
238
- error: finalError,
239
- ...result,
240
- };
241
224
  };
242
225
 
243
226
  const makeMethodFn = (method: Uppercase<HttpMethod>) => (options: RequestOptions) =>
@@ -248,7 +231,6 @@ export const createClient = (config: Config = {}): Client => {
248
231
  return createSseClient({
249
232
  ...opts,
250
233
  body: opts.body as BodyInit | null | undefined,
251
- headers: opts.headers as unknown as Record<string, string>,
252
234
  method,
253
235
  onRequest: async (url, init) => {
254
236
  let request = new Request(url, init);
@@ -125,8 +125,10 @@ export type RequestResult<
125
125
  error: TError extends Record<string, unknown> ? TError[keyof TError] : TError;
126
126
  }
127
127
  ) & {
128
- request: Request;
129
- response: Response;
128
+ /** request may be undefined, because error may be from building the request object itself */
129
+ request?: Request;
130
+ /** response may be undefined, because error may be from building the request object itself or from a network error */
131
+ response?: Response;
130
132
  }
131
133
  >;
132
134
 
@@ -216,8 +216,10 @@ export const mergeHeaders = (
216
216
 
217
217
  type ErrInterceptor<Err, Res, Req, Options> = (
218
218
  error: Err,
219
- response: Res,
220
- request: Req,
219
+ /** response may be undefined due to a network error where no response object is produced */
220
+ response: Res | undefined,
221
+ /** request may be undefined, because error may be from building the request object itself */
222
+ request: Req | undefined,
221
223
  options: Options,
222
224
  ) => Err | Promise<Err>;
223
225