11import { HttpError } from '../errors/http-error' ;
22import { NetworkError } from '../errors/network-error' ;
3- import { TimeoutError } from '../errors/timeout-error' ;
43import type { HeadersMap } from '../types/common' ;
5- import type { ClientConfig , RetryConfig } from '../types/config' ;
4+ import type { ClientConfig } from '../types/config' ;
65import type { RequestConfig } from '../types/request' ;
76import { applyAuth } from './apply-auth' ;
87import { buildUrl } from './build-url' ;
8+ import { applyRequestMetadata } from './apply-request-metadata' ;
9+ import { createExecutionContext } from './execution-context' ;
10+ import { createRequestController } from './create-request-controller' ;
11+ import {
12+ createAfterResponseContext ,
13+ createBeforeRequestContext ,
14+ createErrorContext ,
15+ } from './hook-context' ;
916import { getRetryDelay } from './get-retry-delay' ;
17+ import { normalizeError } from './normalize-error' ;
1018import { parseResponse } from './parse-response' ;
19+ import { resolveRuntimeConfig } from './resolve-runtime-config' ;
1120import { runHooks , runHooksSafely } from './run-hooks' ;
1221import { shouldRetry } from './should-retry' ;
13-
14- const DEFAULT_TIMEOUT = 5000 ;
15-
16- const DEFAULT_RETRY : Required < RetryConfig > = {
17- attempts : 0 ,
18- backoff : 'exponential' ,
19- baseDelayMs : 300 ,
20- retryOn : [ 'network-error' , '5xx' ] ,
21- retryMethods : [ 'GET' , 'PUT' , 'DELETE' ] ,
22- } ;
23-
24- function normalizeError ( error : unknown , timeout : number ) : Error {
25- if ( error instanceof HttpError ) {
26- return error ;
27- }
28-
29- if ( error instanceof Error && error . name === 'AbortError' ) {
30- return new TimeoutError ( timeout , error ) ;
31- }
32-
33- return new NetworkError ( 'Network request failed' , error ) ;
34- }
35-
36- function sleep ( ms : number ) : Promise < void > {
37- return new Promise ( ( resolve ) => {
38- setTimeout ( resolve , ms ) ;
39- } ) ;
40- }
22+ import { sleep } from './sleep' ;
4123
4224export async function request < T > (
4325 clientConfig : ClientConfig ,
4426 requestConfig : RequestConfig ,
4527) : Promise < T > {
46- const fetchImpl = clientConfig . fetch ?? globalThis . fetch ;
47-
48- if ( ! fetchImpl ) {
49- throw new Error ( 'No fetch implementation available' ) ;
50- }
51-
52- const timeout = requestConfig . timeout ?? clientConfig . timeout ?? DEFAULT_TIMEOUT ;
53-
54- const retry : Required < RetryConfig > = {
55- ...DEFAULT_RETRY ,
56- ...( clientConfig . retry ?? { } ) ,
57- ...( requestConfig . retry ?? { } ) ,
58- } ;
28+ const { fetchImpl, timeout, retry } = resolveRuntimeConfig ( clientConfig , requestConfig ) ;
5929
6030 const url = new URL ( buildUrl ( clientConfig . baseUrl , requestConfig . path , requestConfig . query ) ) ;
6131
@@ -68,50 +38,55 @@ export async function request<T>(
6838 ...( requestConfig . headers ?? { } ) ,
6939 } ;
7040
71- await applyAuth ( {
72- auth : clientConfig . auth ,
41+ const execution = createExecutionContext ( {
7342 request : requestConfig ,
7443 url,
7544 headers,
45+ attempt,
7646 } ) ;
7747
78- await runHooks ( clientConfig . hooks ?. beforeRequest , {
79- request : requestConfig ,
80- url,
81- headers,
48+ applyRequestMetadata ( execution ) ;
49+
50+ await applyAuth ( {
51+ auth : clientConfig . auth ,
52+ request : execution . request ,
53+ url : execution . url ,
54+ headers : execution . headers ,
8255 } ) ;
8356
57+ await runHooks ( clientConfig . hooks ?. beforeRequest , createBeforeRequestContext ( execution ) ) ;
58+
8459 let body : BodyInit | undefined ;
8560
86- if ( requestConfig . body !== undefined ) {
87- if ( typeof requestConfig . body === 'string' ) {
88- body = requestConfig . body ;
61+ if ( execution . request . body !== undefined ) {
62+ if ( typeof execution . request . body === 'string' ) {
63+ body = execution . request . body ;
8964 } else {
90- headers [ 'content-type' ] = headers [ 'content-type' ] ?? 'application/json' ;
91- body = JSON . stringify ( requestConfig . body ) ;
65+ execution . headers [ 'content-type' ] = execution . headers [ 'content-type' ] ?? 'application/json' ;
66+ body = JSON . stringify ( execution . request . body ) ;
9267 }
9368 }
9469
95- const controller = new AbortController ( ) ;
96- const timeoutId = setTimeout ( ( ) => {
97- controller . abort ( ) ;
98- } , timeout ) ;
70+ const requestController = createRequestController ( {
71+ timeout ,
72+ signal : execution . request . signal ,
73+ } ) ;
9974
10075 let response : Response ;
10176 let data : unknown ;
10277
10378 try {
10479 const init : RequestInit = {
105- method : requestConfig . method ,
106- headers,
107- signal : controller . signal ,
80+ method : execution . request . method ,
81+ headers : execution . headers ,
82+ signal : requestController . signal ,
10883 } ;
10984
11085 if ( body !== undefined ) {
11186 init . body = body ;
11287 }
11388
114- response = await fetchImpl ( url . toString ( ) , init ) ;
89+ response = await fetchImpl ( execution . url . toString ( ) , init ) ;
11590 data = await parseResponse ( response ) ;
11691
11792 if ( ! response . ok ) {
@@ -122,42 +97,34 @@ export async function request<T>(
12297 lastError = error ;
12398
12499 const canRetry = shouldRetry ( {
125- attempt,
126- method : requestConfig . method ,
100+ attempt : execution . attempt ,
101+ method : execution . request . method ,
127102 retry,
128103 error,
129104 } ) ;
130105
131106 if ( ! canRetry ) {
132- await runHooksSafely ( clientConfig . hooks ?. onError , {
133- request : requestConfig ,
134- url,
135- headers,
136- error,
137- } ) ;
107+ await runHooksSafely ( clientConfig . hooks ?. onError , createErrorContext ( execution , error ) ) ;
138108
139109 throw error ;
140110 }
141111
142112 const delay = getRetryDelay ( {
143- attempt : attempt + 1 ,
113+ attempt : execution . attempt + 1 ,
144114 backoff : retry . backoff ,
145115 baseDelayMs : retry . baseDelayMs ,
146116 } ) ;
147117
148118 await sleep ( delay ) ;
149119 continue ;
150120 } finally {
151- clearTimeout ( timeoutId ) ;
121+ requestController . cleanup ( ) ;
152122 }
153123
154- await runHooks ( clientConfig . hooks ?. afterResponse , {
155- request : requestConfig ,
156- url,
157- headers,
158- response,
159- data,
160- } ) ;
124+ await runHooks (
125+ clientConfig . hooks ?. afterResponse ,
126+ createAfterResponseContext ( execution , response , data ) ,
127+ ) ;
161128
162129 return data as T ;
163130 }
0 commit comments