@hey-api/openapi-ts 0.96.1 → 0.97.0

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.
@@ -72,131 +72,138 @@ export const createClient = (config: Config = {}): Client => {
72
72
 
73
73
  // @ts-expect-error
74
74
  const request: Client['request'] = async (options) => {
75
- const { opts, url } = await beforeRequest(options);
75
+ const throwOnError = options.throwOnError ?? _config.throwOnError;
76
+
77
+ let response: Response | undefined;
78
+
79
+ try {
80
+ const { opts, url } = await beforeRequest(options);
76
81
 
77
- for (const fn of interceptors.request.fns) {
78
- if (fn) {
79
- await fn(opts);
82
+ for (const fn of interceptors.request.fns) {
83
+ if (fn) {
84
+ await fn(opts);
85
+ }
80
86
  }
81
- }
82
87
 
83
- // fetch must be assigned here, otherwise it would throw the error:
84
- // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation
85
- const _fetch = opts.fetch!;
86
- const requestInit: ReqInit = {
87
- ...opts,
88
- body: getValidRequestBody(opts),
89
- };
88
+ // fetch must be assigned here, otherwise it would throw the error:
89
+ // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation
90
+ const _fetch = opts.fetch!;
91
+ const requestInit: ReqInit = {
92
+ ...opts,
93
+ body: getValidRequestBody(opts),
94
+ };
90
95
 
91
- let response = await _fetch(url, requestInit);
96
+ response = await _fetch(url, requestInit);
92
97
 
93
- for (const fn of interceptors.response.fns) {
94
- if (fn) {
95
- response = await fn(response, opts);
98
+ for (const fn of interceptors.response.fns) {
99
+ if (fn) {
100
+ response = await fn(response, opts);
101
+ }
96
102
  }
97
- }
98
103
 
99
- const result = {
100
- response,
101
- };
104
+ const result = {
105
+ response,
106
+ };
102
107
 
103
- if (response.ok) {
104
- const parseAs =
105
- (opts.parseAs === 'auto'
106
- ? getParseAs(response.headers.get('Content-Type'))
107
- : opts.parseAs) ?? 'json';
108
+ if (response.ok) {
109
+ const parseAs =
110
+ (opts.parseAs === 'auto'
111
+ ? getParseAs(response.headers.get('Content-Type'))
112
+ : opts.parseAs) ?? 'json';
113
+
114
+ if (response.status === 204 || response.headers.get('Content-Length') === '0') {
115
+ let emptyData: any;
116
+ switch (parseAs) {
117
+ case 'arrayBuffer':
118
+ case 'blob':
119
+ case 'text':
120
+ emptyData = await response[parseAs]();
121
+ break;
122
+ case 'formData':
123
+ emptyData = new FormData();
124
+ break;
125
+ case 'stream':
126
+ emptyData = response.body;
127
+ break;
128
+ case 'json':
129
+ default:
130
+ emptyData = {};
131
+ break;
132
+ }
133
+ return {
134
+ data: emptyData,
135
+ ...result,
136
+ };
137
+ }
108
138
 
109
- if (response.status === 204 || response.headers.get('Content-Length') === '0') {
110
- let emptyData: any;
139
+ let data: any;
111
140
  switch (parseAs) {
112
141
  case 'arrayBuffer':
113
142
  case 'blob':
143
+ case 'formData':
114
144
  case 'text':
115
- emptyData = await response[parseAs]();
145
+ data = await response[parseAs]();
116
146
  break;
117
- case 'formData':
118
- emptyData = new FormData();
147
+ case 'json': {
148
+ // Some servers return 200 with no Content-Length and empty body.
149
+ // response.json() would throw; read as text and parse if non-empty.
150
+ const text = await response.text();
151
+ data = text ? JSON.parse(text) : {};
119
152
  break;
153
+ }
120
154
  case 'stream':
121
- emptyData = response.body;
122
- break;
123
- case 'json':
124
- default:
125
- emptyData = {};
126
- break;
155
+ return {
156
+ data: response.body,
157
+ ...result,
158
+ };
127
159
  }
160
+
161
+ if (parseAs === 'json') {
162
+ if (opts.responseValidator) {
163
+ await opts.responseValidator(data);
164
+ }
165
+
166
+ if (opts.responseTransformer) {
167
+ data = await opts.responseTransformer(data);
168
+ }
169
+ }
170
+
128
171
  return {
129
- data: emptyData,
172
+ data,
130
173
  ...result,
131
174
  };
132
175
  }
133
176
 
134
- let data: any;
135
- switch (parseAs) {
136
- case 'arrayBuffer':
137
- case 'blob':
138
- case 'formData':
139
- case 'text':
140
- data = await response[parseAs]();
141
- break;
142
- case 'json': {
143
- // Some servers return 200 with no Content-Length and empty body.
144
- // response.json() would throw; read as text and parse if non-empty.
145
- const text = await response.text();
146
- data = text ? JSON.parse(text) : {};
147
- break;
148
- }
149
- case 'stream':
150
- return {
151
- data: response.body,
152
- ...result,
153
- };
177
+ const textError = await response.text();
178
+ let jsonError: unknown;
179
+
180
+ try {
181
+ jsonError = JSON.parse(textError);
182
+ } catch {
183
+ // noop
154
184
  }
155
185
 
156
- if (parseAs === 'json') {
157
- if (opts.responseValidator) {
158
- await opts.responseValidator(data);
159
- }
186
+ throw jsonError ?? textError;
187
+ } catch (error) {
188
+ let finalError = error;
160
189
 
161
- if (opts.responseTransformer) {
162
- data = await opts.responseTransformer(data);
190
+ for (const fn of interceptors.error.fns) {
191
+ if (fn) {
192
+ finalError = await fn(finalError, response, options as ResolvedRequestOptions);
163
193
  }
164
194
  }
165
195
 
166
- return {
167
- data,
168
- ...result,
169
- };
170
- }
196
+ finalError = finalError || {};
171
197
 
172
- const textError = await response.text();
173
- let jsonError: unknown;
174
-
175
- try {
176
- jsonError = JSON.parse(textError);
177
- } catch {
178
- // noop
179
- }
180
-
181
- const error = jsonError ?? textError;
182
- let finalError = error;
183
-
184
- for (const fn of interceptors.error.fns) {
185
- if (fn) {
186
- finalError = (await fn(error, response, opts)) as string;
198
+ if (throwOnError) {
199
+ throw finalError;
187
200
  }
188
- }
189
201
 
190
- finalError = finalError || ({} as string);
191
-
192
- if (opts.throwOnError) {
193
- throw finalError;
202
+ return {
203
+ error: finalError,
204
+ response,
205
+ };
194
206
  }
195
-
196
- return {
197
- error: finalError,
198
- ...result,
199
- };
200
207
  };
201
208
 
202
209
  const makeMethodFn = (method: Uppercase<HttpMethod>) => (options: RequestOptions) =>
@@ -207,18 +214,13 @@ export const createClient = (config: Config = {}): Client => {
207
214
  return createSseClient({
208
215
  ...opts,
209
216
  body: opts.body as BodyInit | null | undefined,
210
- headers: opts.headers as unknown as Record<string, string>,
211
217
  method,
212
218
  onRequest: async (url, init) => {
213
219
  let request = new Request(url, init);
214
- const requestInit = {
215
- ...init,
216
- method: init.method as Config['method'],
217
- url,
218
- };
220
+ const requestInit = { ...init, url };
219
221
  for (const fn of interceptors.request.fns) {
220
222
  if (fn) {
221
- await fn(requestInit as unknown as ResolvedRequestOptions);
223
+ await fn(requestInit as ResolvedRequestOptions);
222
224
  request = new Request(requestInit.url, requestInit);
223
225
  }
224
226
  }
@@ -97,7 +97,8 @@ export type RequestResult<
97
97
  error: TError extends Record<string, unknown> ? TError[keyof TError] : TError;
98
98
  }
99
99
  ) & {
100
- response: Response;
100
+ /** response may be undefined due to a network error where no response object is produced */
101
+ response?: Response;
101
102
  }
102
103
  >;
103
104
 
@@ -325,7 +325,8 @@ export const mergeHeaders = (
325
325
 
326
326
  type ErrInterceptor<Err, Res, Options> = (
327
327
  error: Err,
328
- response: Res,
328
+ /** response may be undefined due to a network error where no response object is produced */
329
+ response: Res | undefined,
329
330
  options: Options,
330
331
  ) => Err | Promise<Err>;
331
332
 
@@ -116,7 +116,7 @@ export const createClient = (config: Config = {}): Client => {
116
116
  // ignore request.body changes to avoid turning serialized bodies into streams
117
117
  // body comes only from getValidRequestBody(options)
118
118
  // reflect signal if present
119
- opts.signal = (request as any).signal as AbortSignal | undefined;
119
+ opts.signal = request.signal;
120
120
 
121
121
  // When body is FormData, remove Content-Type header to avoid boundary mismatch.
122
122
  // Note: We already delete Content-Type in resolveOptions for FormData, but the
@@ -138,92 +138,102 @@ export const createClient = (config: Config = {}): Client => {
138
138
  body: BodyInit | null | undefined,
139
139
  responseType: OfetchResponseType | undefined,
140
140
  ) => {
141
- const effectiveRetry = isRepeatableBody(body) ? (opts.retry as any) : (0 as any);
141
+ const effectiveRetry = isRepeatableBody(body) ? opts.retry : 0;
142
142
  return buildOfetchOptions(opts, body, responseType, effectiveRetry);
143
143
  };
144
144
 
145
145
  const request: Client['request'] = async (options) => {
146
- const { networkBody: initialNetworkBody, opts, url } = await resolveOptions(options as any);
147
- // map parseAs -> ofetch responseType once per request
148
- const ofetchResponseType: OfetchResponseType | undefined = mapParseAsToResponseType(
149
- opts.parseAs,
150
- opts.responseType,
151
- );
152
-
153
- const $ofetch = opts.ofetch ?? ofetch;
154
-
155
- // create Request before network to run middleware consistently
156
- const networkBody = initialNetworkBody;
157
- const requestInit: ReqInit = {
158
- body: networkBody,
159
- headers: opts.headers as Headers,
160
- method: opts.method,
161
- redirect: 'follow',
162
- signal: opts.signal,
163
- };
164
- let request = new Request(url, requestInit);
165
-
166
- request = await applyRequestInterceptors(request, opts, networkBody);
167
- const finalUrl = request.url;
168
-
169
- // build ofetch options and perform the request (.raw keeps the Response)
170
- const responseOptions = buildNetworkOptions(
171
- opts as ResolvedRequestOptions,
172
- networkBody,
173
- ofetchResponseType,
174
- );
146
+ const throwOnError = options.throwOnError ?? _config.throwOnError;
147
+ const responseStyle = options.responseStyle ?? _config.responseStyle;
148
+
149
+ let request: Request | undefined;
150
+ let response: Awaited<ReturnType<typeof ofetch.raw>> | undefined;
151
+
152
+ try {
153
+ const {
154
+ networkBody: initialNetworkBody,
155
+ opts,
156
+ url,
157
+ } = await resolveOptions(options as RequestOptions);
158
+ // map parseAs -> ofetch responseType once per request
159
+ const ofetchResponseType: OfetchResponseType | undefined = mapParseAsToResponseType(
160
+ opts.parseAs,
161
+ opts.responseType,
162
+ );
163
+
164
+ const $ofetch = opts.ofetch ?? ofetch;
165
+
166
+ // create Request before network to run middleware consistently
167
+ const networkBody = initialNetworkBody;
168
+ const requestInit: ReqInit = {
169
+ body: networkBody,
170
+ headers: opts.headers,
171
+ method: opts.method,
172
+ redirect: 'follow',
173
+ signal: opts.signal,
174
+ };
175
+ request = new Request(url, requestInit);
176
+
177
+ request = await applyRequestInterceptors(request, opts, networkBody);
178
+ const finalUrl = request.url;
179
+
180
+ // build ofetch options and perform the request (.raw keeps the Response)
181
+ const responseOptions = buildNetworkOptions(opts, networkBody, ofetchResponseType);
182
+
183
+ response = await $ofetch.raw(finalUrl, responseOptions);
184
+
185
+ for (const fn of interceptors.response.fns) {
186
+ if (fn) {
187
+ response = await fn(response, request, opts);
188
+ }
189
+ }
175
190
 
176
- let response = await $ofetch.raw(finalUrl, responseOptions);
191
+ const result = { request, response };
177
192
 
178
- for (const fn of interceptors.response.fns) {
179
- if (fn) {
180
- response = await fn(response, request, opts);
193
+ if (response.ok) {
194
+ const data = await parseSuccess(response, opts, ofetchResponseType);
195
+ return wrapDataReturn(data, result, opts.responseStyle);
181
196
  }
182
- }
183
197
 
184
- const result = { request, response };
198
+ throw await parseError(response);
199
+ } catch (error) {
200
+ let finalError = error;
185
201
 
186
- if (response.ok) {
187
- const data = await parseSuccess(response, opts, ofetchResponseType);
188
- return wrapDataReturn(data, result, opts.responseStyle);
189
- }
202
+ for (const fn of interceptors.error.fns) {
203
+ if (fn) {
204
+ finalError = await fn(finalError, response, request, options as ResolvedRequestOptions);
205
+ }
206
+ }
190
207
 
191
- let finalError = await parseError(response);
208
+ // ensure error is never undefined after interceptors
209
+ finalError = finalError || ({} as string);
192
210
 
193
- for (const fn of interceptors.error.fns) {
194
- if (fn) {
195
- finalError = await fn(finalError, response, request, opts);
211
+ if (throwOnError) {
212
+ throw finalError;
196
213
  }
197
- }
198
-
199
- // ensure error is never undefined after interceptors
200
- finalError = (finalError as any) || ({} as string);
201
214
 
202
- if (opts.throwOnError) {
203
- throw finalError;
215
+ return wrapErrorReturn(finalError, { request, response }, responseStyle) as any;
204
216
  }
205
-
206
- return wrapErrorReturn(finalError, result, opts.responseStyle) as any;
207
217
  };
208
218
 
209
219
  const makeMethodFn = (method: Uppercase<HttpMethod>) => (options: RequestOptions) =>
210
- request({ ...options, method } as any);
220
+ request({ ...options, method });
211
221
 
212
222
  const makeSseFn = (method: Uppercase<HttpMethod>) => async (options: RequestOptions) => {
213
223
  const { networkBody, opts, url } = await resolveOptions(options);
214
- const optsForSse: any = { ...opts };
224
+ const optsForSse = { ...opts };
215
225
  delete optsForSse.body; // body is provided via serializedBody below
216
226
  return createSseClient({
217
- ...optsForSse,
227
+ ...(optsForSse as Omit<typeof opts, 'body'>),
218
228
  fetch: opts.fetch,
219
- headers: opts.headers as Headers,
229
+ headers: opts.headers,
220
230
  method,
221
231
  onRequest: async (url, init) => {
222
232
  let request = new Request(url, init);
223
233
  request = await applyRequestInterceptors(request, opts, networkBody);
224
234
  return request;
225
235
  },
226
- serializedBody: networkBody as BodyInit | null | undefined,
236
+ serializedBody: networkBody,
227
237
  signal: opts.signal,
228
238
  url,
229
239
  });
@@ -188,8 +188,10 @@ export type RequestResult<
188
188
  error: TError extends Record<string, unknown> ? TError[keyof TError] : TError;
189
189
  }
190
190
  ) & {
191
- request: Request;
192
- response: Response;
191
+ /** request may be undefined, because error may be from building the request object itself */
192
+ request?: Request;
193
+ /** response may be undefined due to a network error where no response object is produced */
194
+ response?: Response;
193
195
  }
194
196
  >;
195
197
 
@@ -277,7 +277,7 @@ export const wrapDataReturn = <T>(
277
277
  */
278
278
  export const wrapErrorReturn = <E>(
279
279
  error: E,
280
- result: { request: Request; response: Response },
280
+ result: { request: Request | undefined; response: Response | undefined },
281
281
  responseStyle: ResponseStyle | undefined,
282
282
  ):
283
283
  | undefined
@@ -405,8 +405,10 @@ export const parseError = async (response: Response): Promise<unknown> => {
405
405
 
406
406
  type ErrInterceptor<Err, Res, Req, Options> = (
407
407
  error: Err,
408
- response: Res,
409
- request: Req,
408
+ /** response may be undefined due to a network error where no response object is produced */
409
+ response: Res | undefined,
410
+ /** request may be undefined, because error may be from building the request object itself */
411
+ request: Req | undefined,
410
412
  options: Options,
411
413
  ) => Err | Promise<Err>;
412
414