@@ -183,91 +183,68 @@ describe("multiple success responses", () => {
183183
184184 export type Fetcher = (method: Method, url: string, parameters?: EndpointParameters | undefined) => Promise<Response>;
185185
186- // Status code type for success responses
187- export type SuccessStatusCode =
188- | 200
189- | 201
190- | 202
191- | 203
192- | 204
193- | 205
194- | 206
195- | 207
196- | 208
197- | 226
198- | 300
199- | 301
200- | 302
201- | 303
202- | 304
203- | 305
204- | 306
205- | 307
206- | 308;
186+ export const successStatusCodes = [
187+ 200, 201, 202, 203, 204, 205, 206, 207, 208, 226, 300, 301, 302, 303, 304, 305, 306, 307, 308,
188+ ] as const;
189+ export type SuccessStatusCode = (typeof successStatusCodes)[number];
190+
191+ export const errorStatusCodes = [
192+ 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 421, 422, 423, 424,
193+ 425, 426, 428, 429, 431, 451, 500, 501, 502, 503, 504, 505, 506, 507, 508, 510, 511,
194+ ] as const;
195+ export type ErrorStatusCode = (typeof errorStatusCodes)[number];
207196
208197 // Error handling types
198+ /** @see https://developer.mozilla.org/en-US/docs/Web/API/Response */
199+ interface SuccessResponse<TSuccess, TStatusCode> extends Omit<Response, "ok" | "status" | "json"> {
200+ ok: true;
201+ status: TStatusCode;
202+ data: TSuccess;
203+ /** [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/Response/json) */
204+ json: () => Promise<TSuccess>;
205+ }
206+
207+ /** @see https://developer.mozilla.org/en-US/docs/Web/API/Response */
208+ interface ErrorResponse<TData, TStatusCode> extends Omit<Response, "ok" | "status" | "json"> {
209+ ok: false;
210+ status: TStatusCode;
211+ data: TData;
212+ /** [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/Response/json) */
213+ json: () => Promise<TData>;
214+ }
215+
209216 export type TypedApiResponse<
210217 TSuccess,
211218 TAllResponses extends Record<string | number, unknown> = {},
212219 > = keyof TAllResponses extends never
213- ? Omit<Response, "ok" | "status" | "json"> & {
214- ok: true;
215- status: number;
216- data: TSuccess;
217- json: () => Promise<TSuccess>;
218- }
220+ ? SuccessResponse<TSuccess, number>
219221 : {
220222 [K in keyof TAllResponses]: K extends string
221223 ? K extends \`\${infer TStatusCode extends number}\`
222224 ? TStatusCode extends SuccessStatusCode
223- ? Omit<Response, "ok" | "status" | "json"> & {
224- ok: true;
225- status: TStatusCode;
226- data: TSuccess;
227- json: () => Promise<TSuccess>;
228- }
229- : Omit<Response, "ok" | "status" | "json"> & {
230- ok: false;
231- status: TStatusCode;
232- data: TAllResponses[K];
233- json: () => Promise<TAllResponses[K]>;
234- }
225+ ? SuccessResponse<TSuccess, TStatusCode>
226+ : ErrorResponse<TAllResponses[K], TStatusCode>
235227 : never
236228 : K extends number
237229 ? K extends SuccessStatusCode
238- ? Omit<Response, "ok" | "status" | "json"> & {
239- ok: true;
240- status: K;
241- data: TSuccess;
242- json: () => Promise<TSuccess>;
243- }
244- : Omit<Response, "ok" | "status" | "json"> & {
245- ok: false;
246- status: K;
247- data: TAllResponses[K];
248- json: () => Promise<TAllResponses[K]>;
249- }
230+ ? SuccessResponse<TSuccess, K>
231+ : ErrorResponse<TAllResponses[K], K>
250232 : never;
251233 }[keyof TAllResponses];
252234
253235 export type SafeApiResponse<TEndpoint> = TEndpoint extends { response: infer TSuccess; responses: infer TResponses }
254236 ? TResponses extends Record<string, unknown>
255237 ? TypedApiResponse<TSuccess, TResponses>
256- : Omit<Response, "ok" | "status" | "json"> & {
257- ok: true;
258- status: number;
259- data: TSuccess;
260- json: () => Promise<TSuccess>;
261- }
238+ : SuccessResponse<TSuccess, number>
262239 : TEndpoint extends { response: infer TSuccess }
263- ? Omit<Response, "ok" | "status" | "json"> & {
264- ok: true;
265- status: number;
266- data: TSuccess;
267- json: () => Promise<TSuccess>;
268- }
240+ ? SuccessResponse<TSuccess, number>
269241 : never;
270242
243+ export type InferResponseByStatus<TEndpoint, TStatusCode> = Extract<
244+ SafeApiResponse<TEndpoint>,
245+ { status: TStatusCode }
246+ >;
247+
271248 type RequiredKeys<T> = {
272249 [P in keyof T]-?: undefined extends T[P] ? never : P;
273250 }[keyof T];
@@ -276,9 +253,23 @@ describe("multiple success responses", () => {
276253
277254 // </ApiClientTypes>
278255
256+ // <TypedResponseError>
257+ export class TypedResponseError extends Error {
258+ response: ErrorResponse<unknown, ErrorStatusCode>;
259+ status: number;
260+ constructor(response: ErrorResponse<unknown, ErrorStatusCode>) {
261+ super(\`HTTP \${response.status}: \${response.statusText}\`);
262+ this.name = "TypedResponseError";
263+ this.response = response;
264+ this.status = response.status;
265+ }
266+ }
267+ // </TypedResponseError>
279268 // <ApiClient>
280269 export class ApiClient {
281270 baseUrl: string = "";
271+ successStatusCodes = successStatusCodes;
272+ errorStatusCodes = errorStatusCodes;
282273
283274 constructor(public fetcher: Fetcher) {}
284275
@@ -298,12 +289,12 @@ describe("multiple success responses", () => {
298289 // <ApiClient.post>
299290 post<Path extends keyof PostEndpoints, TEndpoint extends PostEndpoints[Path]>(
300291 path: Path,
301- ...params: MaybeOptionalArg<TEndpoint["parameters"] & { withResponse?: false }>
292+ ...params: MaybeOptionalArg<TEndpoint["parameters"] & { withResponse?: false; throwOnStatusError?: boolean }>
302293 ): Promise<TEndpoint["response"]>;
303294
304295 post<Path extends keyof PostEndpoints, TEndpoint extends PostEndpoints[Path]>(
305296 path: Path,
306- ...params: MaybeOptionalArg<TEndpoint["parameters"] & { withResponse: true }>
297+ ...params: MaybeOptionalArg<TEndpoint["parameters"] & { withResponse: true; throwOnStatusError?: boolean }>
307298 ): Promise<SafeApiResponse<TEndpoint>>;
308299
309300 post<Path extends keyof PostEndpoints, TEndpoint extends PostEndpoints[Path]>(
@@ -312,31 +303,27 @@ describe("multiple success responses", () => {
312303 ): Promise<any> {
313304 const requestParams = params[0];
314305 const withResponse = requestParams?.withResponse;
306+ const { withResponse: _, throwOnStatusError = withResponse ? false : true, ...fetchParams } = requestParams || {};
307+
308+ const promise = this.fetcher(
309+ "post",
310+ this.baseUrl + path,
311+ Object.keys(fetchParams).length ? requestParams : undefined,
312+ ).then(async (response) => {
313+ const data = await this.parseResponse(response);
314+ const typedResponse = Object.assign(response, {
315+ data: data,
316+ json: () => Promise.resolve(data),
317+ }) as SafeApiResponse<TEndpoint>;
318+
319+ if (throwOnStatusError && errorStatusCodes.includes(response.status as never)) {
320+ throw new TypedResponseError(typedResponse as never);
321+ }
315322
316- // Remove withResponse from params before passing to fetcher
317- const { withResponse: _, ...fetchParams } = requestParams || {};
318-
319- if (withResponse) {
320- return this.fetcher("post", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then(
321- async (response) => {
322- // Parse the response data
323- const data = await this.parseResponse(response);
324-
325- // Override properties while keeping the original Response object
326- const typedResponse = Object.assign(response, {
327- ok: response.ok,
328- status: response.status,
329- data: data,
330- json: () => Promise.resolve(data),
331- });
332- return typedResponse;
333- },
334- );
335- } else {
336- return this.fetcher("post", this.baseUrl + path, requestParams).then((response) =>
337- this.parseResponse(response),
338- ) as Promise<TEndpoint["response"]>;
339- }
323+ return withResponse ? typedResponse : data;
324+ });
325+
326+ return promise as Promise<TEndpoint["response"]>;
340327 }
341328 // </ApiClient.post>
342329
@@ -352,13 +339,10 @@ describe("multiple success responses", () => {
352339 method: TMethod,
353340 path: TPath,
354341 ...params: MaybeOptionalArg<TEndpoint extends { parameters: infer Params } ? Params : never>
355- ): Promise<
356- Omit<Response, "json"> & {
357- /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/json) */
358- json: () => Promise<TEndpoint extends { response: infer Res } ? Res : never>;
359- }
360- > {
361- return this.fetcher(method, this.baseUrl + (path as string), params[0] as EndpointParameters);
342+ ): Promise<SafeApiResponse<TEndpoint>> {
343+ return this.fetcher(method, this.baseUrl + (path as string), params[0] as EndpointParameters) as Promise<
344+ SafeApiResponse<TEndpoint>
345+ >;
362346 }
363347 // </ApiClient.request>
364348 }
@@ -392,7 +376,7 @@ describe("multiple success responses", () => {
392376 }
393377 */
394378
395- // </ApiClient
379+ // </ApiClient>
396380 "
397381 ` ) ;
398382 } ) ;
0 commit comments