Skip to main content
Glama
fetcher.ts5.86 kB
/** * Type definition for options used in the fetcher function. * Extends the standard RequestInit interface (excluding 'body'), * and adds 'body' and 'params' properties for convenience. */ export type FetcherOptions = Omit<RequestInit, 'body'> & { /** * Body of the request. Should be a key-value pair object. */ body?: Record<string, unknown>; /** * Query parameters to be appended to the URL. */ params?: | Record<string, string | string[] | undefined> | string[] | URLSearchParams; }; /** * Default options for the fetcher function. * Sets the default method to 'GET', the 'Content-Type' header to 'application/json', * and includes credentials in the request. */ export const fetcherOptions: FetcherOptions = { method: 'GET', // Default HTTP method headers: { 'Content-Type': 'application/json', // Default content type }, credentials: 'include', }; /** * Utility function to remove properties with undefined values from an object. * This helps prevent sending undefined values in the request options. * * @param obj - The object to clean. * @returns The cleaned object without undefined values. */ const removeUndefined = (obj: object) => { Object.keys(obj).forEach((key) => { if (obj[key as keyof typeof obj] === undefined) { delete obj[key as keyof typeof obj]; } }); return obj; }; /** * Deeply merges an array of objects into a single object. * Later objects in the array overwrite properties of earlier ones. * * @template T - The type of objects being merged. * @param objects - An array of objects to merge. * @returns The merged object. */ const deepMerge = <T extends object>(objects: (T | undefined)[]): T => objects.reduce((acc, obj) => { const acc1: T = (acc ?? {}) as T; const obj1: T = removeUndefined(obj ?? {}) as T; if (typeof acc1 === 'object' && typeof obj1 === 'object') { // Merge the two objects return { ...acc1, ...obj1 }; } // If one of them is not an object, return the defined one return obj1 ?? acc1; }, {} as T)!; /** * Fetcher function to make HTTP requests. * It merges default options with user-provided options, * handles query parameters and request body, * and returns the parsed JSON response. * * @template T - The expected type of the response data. * @param url - The endpoint URL. * @param options - Additional options to customize the request. * @returns A promise that resolves with the response data of type T. * * @example * ```typescript * // Making a GET request with query parameters * const data = await fetcher<MyResponseType>('https://api.example.com/data', { * params: { search: 'query' }, * }); * * // Making a POST request with a JSON body * const result = await fetcher<AnotherResponseType>('https://api.example.com/submit', { * method: 'POST', * body: { key: 'value' }, * }); * * // Merge body, headers, and params * const result = await fetcher<AnotherResponseType>('https://api.example.com/submit', { * method: 'POST', * body: { key: 'value' }, * headers: { 'Content-Type': 'application/json' }, * params: { search: 'query' }, * }, * { * headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, * params: { page: 1 }, * }); * ``` * * Result: * ```typescript * { * method: 'POST', * headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, * params: { page: 1, search: 'query' }, * body: { key: 'value' }, * } * ``` */ export const fetcher = async <T>( url: string, ...options: FetcherOptions[] ): Promise<T> => { const { signal } = new AbortController(); // Initialize query parameters string and request body string let paramsString = ''; let bodyString: string | undefined; // Extract other options excluding 'body', 'params', and 'headers' const otherOptions = options.map( ({ body, params, headers, ...otherOptions }) => otherOptions ); // Merge default options with user-provided options const mergedOptions = deepMerge([fetcherOptions, ...otherOptions]); // Merge default headers with user-provided headers const mergedHeaders = deepMerge([ fetcherOptions.headers, ...options.map((option) => option.headers), ]); // Merge query parameters const params = deepMerge(options.map((option) => option.params)); const method = mergedOptions.method; // If the request method is not 'GET' or 'HEAD', prepare the request body if (method !== 'GET' && method !== 'HEAD') { // Merge all 'body' options const body = deepMerge(options.map((option) => option.body)); if ( mergedHeaders?.['Content-Type' as keyof HeadersInit] === 'application/x-www-form-urlencoded' ) { // If the content type is 'application/x-www-form-urlencoded', encode the body accordingly bodyString = new URLSearchParams( body as Record<string, string> ).toString(); } else { // Otherwise, stringify the body as JSON bodyString = JSON.stringify(body); } } // If there are query parameters, append them to the URL if (Object.entries(params).length > 0) { paramsString = `?${new URLSearchParams( params as Record<string, string> ).toString()}`; } // Prepare the final request options const formattedOptions: RequestInit = { ...mergedOptions, headers: mergedHeaders, body: bodyString, signal, }; // Construct the full URL with query parameters const urlResult = `${url}${paramsString}`; // Make the HTTP request using fetch const response = await fetch(urlResult, formattedOptions); if (!response.ok) { const result = await response.json(); // You can customize the error message or include more details throw new Error(JSON.stringify(result.error) ?? 'An error occurred'); } return await response.json(); };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/aymericzip/intlayer'

If you have feedback or need assistance with the MCP directory API, please join our Discord server