@@ -190,21 +190,18 @@ export class HttpRequests {
190190 }
191191
192192 /**
193- * Prepares common request parameters and executes the request .
193+ * Prepares common request parameters (URL and RequestInit) .
194194 *
195- * @returns Object containing the prepared URL, RequestInit, and stop timeout
196- * function
195+ * @returns Object containing the prepared URL and RequestInit
197196 */
198- async #prepareAndExecuteRequest( options : MainRequestOptions ) : Promise < {
199- url : URL ;
200- init : RequestInit ;
201- response ?: Response ;
202- stopTimeout : ( ( ) => void ) | null ;
203- customResult ?: unknown ;
204- } > {
205- const { path, method, params, contentType, body, extraRequestInit } =
206- options ;
207-
197+ #prepareRequest( {
198+ path,
199+ method,
200+ params,
201+ contentType,
202+ body,
203+ extraRequestInit,
204+ } : MainRequestOptions ) : { url : URL ; init : RequestInit } {
208205 const url = new URL ( path , this . #url) ;
209206 if ( params !== undefined ) {
210207 appendRecordToURLSearchParams ( url . searchParams , params ) ;
@@ -221,111 +218,59 @@ export class HttpRequests {
221218 headers : this . #getHeaders( extraRequestInit ?. headers , contentType ) ,
222219 } ;
223220
221+ return { url, init } ;
222+ }
223+
224+ /**
225+ * Sends a request with {@link fetch} or a custom HTTP client, combining
226+ * parameters and class properties.
227+ *
228+ * @returns A promise containing the response
229+ */
230+ async #request< T = unknown > ( options : MainRequestOptions ) : Promise < T > {
231+ const { url, init } = this . #prepareRequest( options ) ;
232+
224233 const startTimeout =
225234 this . #requestTimeout !== undefined
226235 ? getTimeoutFn ( init , this . #requestTimeout)
227236 : null ;
228237
229- const stopTimeout = startTimeout ?.( ) || null ;
238+ const stopTimeout = startTimeout ?.( ) ;
230239
240+ let response : Response ;
241+ let responseBody : string ;
231242 try {
232243 if ( this . #customRequestFn !== undefined ) {
233- const customResult = await this . #customRequestFn ( url , init ) ;
234- return { url, init, stopTimeout , customResult } ;
244+ // When using a custom HTTP client, the response should already be handled and ready to be returned
245+ return ( await this . #customRequestFn ( url , init ) ) as T ;
235246 }
236247
237- const response = await fetch ( url , init ) ;
238- return { url , init , response, stopTimeout } ;
248+ response = await fetch ( url , init ) ;
249+ responseBody = await response . text ( ) ;
239250 } catch ( error ) {
240- stopTimeout ?.( ) ;
241251 throw new MeiliSearchRequestError (
242252 url . toString ( ) ,
243253 Object . is ( error , TIMEOUT_ID )
244254 ? new MeiliSearchRequestTimeOutError ( this . #requestTimeout! , init )
245255 : error ,
246256 ) ;
257+ } finally {
258+ stopTimeout ?.( ) ;
247259 }
248- }
249260
250- /** Validates that a custom HTTP client result is of the expected type. */
251- #validateCustomClientResult< T > (
252- result : unknown ,
253- expectedType : "json" | "stream" ,
254- url : string ,
255- ) : T {
256- if ( expectedType === "stream" ) {
257- if ( ! ( result instanceof ReadableStream ) ) {
258- throw new MeiliSearchError (
259- `Custom HTTP client must return a ReadableStream for streaming requests. Got ${ typeof result } instead. URL: ${ url } ` ,
260- ) ;
261- }
262- }
263- return result as T ;
264- }
261+ const parsedResponse =
262+ responseBody === ""
263+ ? undefined
264+ : ( JSON . parse ( responseBody ) as T | MeiliSearchErrorResponse ) ;
265265
266- /**
267- * Handles API error responses by reading the response body and throwing
268- * appropriate errors.
269- */
270- async #handleApiError( response : Response , url : string ) : Promise < never > {
271- try {
272- const responseBody = await response . text ( ) ;
273- const parsedResponse =
274- responseBody === ""
275- ? undefined
276- : ( JSON . parse ( responseBody ) as MeiliSearchErrorResponse ) ;
277-
278- throw new MeiliSearchApiError ( response , parsedResponse ) ;
279- } catch ( error ) {
280- if ( error instanceof MeiliSearchApiError ) {
281- throw error ;
282- }
283- throw new MeiliSearchError (
284- `Failed to parse error response from ${ url } : ${ error instanceof Error ? error . message : String ( error ) } ` ,
266+ if ( ! response . ok ) {
267+ throw new MeiliSearchApiError (
268+ response ,
269+ parsedResponse as MeiliSearchErrorResponse | undefined ,
285270 ) ;
286271 }
287- }
288-
289- /**
290- * Sends a request with {@link fetch} or a custom HTTP client, combining
291- * parameters and class properties.
292- *
293- * @returns A promise containing the response
294- */
295- async #request< T = unknown > ( options : MainRequestOptions ) : Promise < T > {
296- const { url, response, stopTimeout, customResult } =
297- await this . #prepareAndExecuteRequest( options ) ;
298-
299- try {
300- if ( customResult !== undefined ) {
301- // When using a custom HTTP client, the response should already be handled and ready to be returned
302- return this . #validateCustomClientResult< T > (
303- customResult ,
304- "json" ,
305- url . toString ( ) ,
306- ) ;
307- }
308272
309- if ( ! response ) {
310- throw new MeiliSearchError (
311- `No response received from ${ url . toString ( ) } ` ,
312- ) ;
313- }
314-
315- if ( ! response . ok ) {
316- await this . #handleApiError( response , url . toString ( ) ) ;
317- }
318-
319- const responseBody = await response . text ( ) ;
320- const parsedResponse =
321- responseBody === ""
322- ? undefined
323- : ( JSON . parse ( responseBody ) as T | MeiliSearchErrorResponse ) ;
324-
325- return parsedResponse as T ;
326- } finally {
327- stopTimeout ?.( ) ;
328- }
273+ return parsedResponse as T ;
329274 }
330275
331276 /** Request with GET. */
@@ -366,39 +311,56 @@ export class HttpRequests {
366311 async #requestStream(
367312 options : MainRequestOptions ,
368313 ) : Promise < ReadableStream < Uint8Array > > {
369- const { url, response, stopTimeout, customResult } =
370- await this . #prepareAndExecuteRequest( options ) ;
314+ const { url, init } = this . #prepareRequest( options ) ;
371315
372- try {
373- if ( customResult !== undefined ) {
374- // Custom HTTP clients should return the stream directly
375- return this . #validateCustomClientResult< ReadableStream < Uint8Array > > (
376- customResult ,
377- "stream" ,
378- url . toString ( ) ,
379- ) ;
380- }
381-
382- if ( ! response ) {
383- throw new MeiliSearchError (
384- `No response received from ${ url . toString ( ) } ` ,
385- ) ;
386- }
316+ const startTimeout =
317+ this . #requestTimeout !== undefined
318+ ? getTimeoutFn ( init , this . #requestTimeout)
319+ : null ;
387320
388- if ( ! response . ok ) {
389- await this . #handleApiError( response , url . toString ( ) ) ;
390- }
321+ const stopTimeout = startTimeout ?.( ) ;
391322
392- if ( ! response . body ) {
393- throw new MeiliSearchError (
394- `Response body is not available for streaming from ${ url . toString ( ) } . ` +
395- `This may indicate a server error or unsupported streaming endpoint.` ,
396- ) ;
323+ let response : Response ;
324+ try {
325+ if ( this . #customRequestFn !== undefined ) {
326+ const result = await this . #customRequestFn( url , init ) ;
327+ if ( ! ( result instanceof ReadableStream ) ) {
328+ throw new MeiliSearchError (
329+ "Custom HTTP client must return a ReadableStream for streaming requests" ,
330+ ) ;
331+ }
332+ return result as ReadableStream < Uint8Array > ;
397333 }
398334
399- return response . body ;
335+ response = await fetch ( url , init ) ;
336+ } catch ( error ) {
337+ throw new MeiliSearchRequestError (
338+ url . toString ( ) ,
339+ Object . is ( error , TIMEOUT_ID )
340+ ? new MeiliSearchRequestTimeOutError ( this . #requestTimeout! , init )
341+ : error ,
342+ ) ;
400343 } finally {
401344 stopTimeout ?.( ) ;
402345 }
346+
347+ if ( ! response . ok ) {
348+ // For error responses, we still need to read the body to get error details
349+ const responseBody = await response . text ( ) ;
350+ const parsedResponse =
351+ responseBody === ""
352+ ? undefined
353+ : ( JSON . parse ( responseBody ) as MeiliSearchErrorResponse ) ;
354+
355+ throw new MeiliSearchApiError ( response , parsedResponse ) ;
356+ }
357+
358+ if ( ! response . body ) {
359+ throw new MeiliSearchError (
360+ "Response body is null - server did not return a readable stream" ,
361+ ) ;
362+ }
363+
364+ return response . body ;
403365 }
404366}
0 commit comments