@cardbrother/up-fetch
Version:
Advanced fetch client builder for typescript.
1 lines • 18.4 kB
Source Map (JSON)
{"version":3,"sources":["../src/index.ts","../src/response-error.ts","../src/validation-error.ts","../src/utils.ts","../src/fallback-options.ts","../src/resolve-options.ts","../src/up.ts"],"sourcesContent":["export { up } from './up'\nexport type { FetcherOptions, DefaultOptions, ResolvedOptions } from './types'\nexport { ResponseError, isResponseError } from './response-error'\nexport { type ValidationError, isValidationError } from './validation-error'\nexport { isJsonifiable } from './utils'\n","import type { BaseFetchFn, ResolvedOptions } from './types'\n\nexport class ResponseError<\n TData = any,\n TFetchFn extends BaseFetchFn = typeof fetch,\n> extends Error {\n override name: 'ResponseError'\n response: Response\n options: ResolvedOptions<TFetchFn>\n data: TData\n status: number\n\n constructor(res: Response, data: TData, options: ResolvedOptions<TFetchFn>) {\n super(`Request failed with status ${res.status}`)\n this.data = data\n this.name = 'ResponseError'\n this.response = res\n this.status = res.status\n this.options = options\n }\n}\n\nexport let isResponseError = <\n TData = any,\n TFetchFn extends BaseFetchFn = typeof fetch,\n>(\n error: any,\n): error is ResponseError<TData, TFetchFn> => error instanceof ResponseError\n","import type { StandardSchemaV1 } from '@standard-schema/spec'\n\nexport class ValidationError<TData = any> extends Error {\n override name: 'ValidationError'\n issues: readonly StandardSchemaV1.Issue[]\n data: TData\n\n constructor(result: StandardSchemaV1.FailureResult, data: TData) {\n super('Validation error')\n this.name = 'ValidationError'\n this.issues = result.issues\n this.data = data\n }\n}\n\nexport let isValidationError = (error: any): error is ValidationError =>\n error instanceof ValidationError\n","import type { StandardSchemaV1 } from '@standard-schema/spec'\nimport type {\n Params,\n RawHeaders,\n JsonifiableObject,\n JsonifiableArray,\n} from './types'\nimport { ValidationError } from './validation-error'\n\nexport let mergeHeaders = (...headerInits: (RawHeaders | undefined)[]) => {\n let res: Record<string, string> = {}\n headerInits.forEach((init) => {\n // casting `init` to `HeadersInit` because `Record<string, any>` is\n // properly transformed to `Record<string,string>` by `new Headers(init)`\n new Headers(init as HeadersInit | undefined).forEach((value, key) => {\n if (value === 'null' || value === 'undefined') {\n delete res[key]\n } else {\n res[key] = value\n }\n })\n })\n return res\n}\n\nexport let mergeSignal = (\n signal: AbortSignal | undefined,\n timeout: number | undefined,\n): AbortSignal | undefined =>\n // if AbortSignal.any is not supported\n // AbortSignal.timeout is not supported either\n 'any' in AbortSignal\n ? AbortSignal.any(\n [signal, timeout && AbortSignal.timeout(timeout)].filter(\n Boolean,\n ) as AbortSignal[],\n )\n : signal\n\nexport let resolveParams = (\n defaultParams: Params | undefined,\n input: unknown,\n fetcherParams: Params | undefined,\n): Params =>\n typeof input !== 'string'\n ? {} // an input of type Request cannot use the \"params\" option\n : stripUndefined({\n // Removing the 'url.searchParams.keys()' from the defaultParams\n // but not from the 'fetcherParams'. The user is responsible for not\n // specifying the params in both the \"input\" and the fetcher \"params\" option.\n ...omit(defaultParams, [\n ...new URL(input, 'http://a').searchParams.keys(),\n ]),\n ...fetcherParams,\n })\n\ntype KeyOf<O> = O extends unknown ? keyof O : never\n\nexport type DistributiveOmit<\n TObject extends object,\n TKey extends KeyOf<TObject> | (string & {}),\n> = TObject extends unknown ? Omit<TObject, TKey> : never\n\nexport type MaybePromise<T> = T | Promise<T>\n\nexport let omit = <O extends object, K extends KeyOf<O> | (string & {})>(\n obj?: O,\n keys: K[] | readonly K[] = [],\n): DistributiveOmit<O, K> => {\n let copy = { ...obj } as DistributiveOmit<O, K>\n for (let key in copy) {\n if (keys.includes(key as any as K)) delete copy[key]\n }\n return copy\n}\n\nexport let stripUndefined = <O extends object>(obj?: O): O => {\n let copy = { ...obj } as O\n for (let key in copy) {\n if (copy[key] === undefined) delete copy[key]\n }\n return copy\n}\n\nexport let isJsonifiable = (\n value: any,\n): value is JsonifiableObject | JsonifiableArray => {\n // bun FormData has a toJSON method\n if (!value || typeof value !== 'object' || value instanceof FormData)\n return false\n return (\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n value?.constructor?.name === 'Object' ||\n Array.isArray(value) ||\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n typeof value?.toJSON === 'function'\n )\n}\n\nexport let emptyOptions: any = {}\n\nexport function getUrl(\n base: string | undefined = '',\n input: unknown,\n queryString: string,\n): any {\n if (typeof input !== 'string') return input\n let url = /^https?:\\/\\//.test(input)\n ? input\n : !base || !input\n ? base + input\n : base.replace(/\\/$/, '') + '/' + input.replace(/^\\//, '')\n\n if (queryString) {\n url += (url.includes('?') ? '&' : '?') + queryString.replace(/^\\?/, '')\n }\n return url\n}\n\nexport async function validate<TSchema extends StandardSchemaV1>(\n schema: TSchema,\n data: StandardSchemaV1.InferInput<TSchema>,\n): Promise<StandardSchemaV1.InferOutput<TSchema>> {\n let result = await schema['~standard'].validate(data)\n if (result.issues) throw new ValidationError(result, data)\n return result.value\n}\n\n\nexport function isPromise(f: any): boolean {\n const fnContent = f.toString();\n return Object.prototype.toString.call(f) === '[object AsyncFunction]' || fnContent.includes(\"return _regenerator.default.async(function\")\n}","import { ResponseError } from './response-error'\nimport type { FallbackOptions } from './types'\nimport { isJsonifiable } from './utils'\n\nexport let fallbackOptions: FallbackOptions<any> = {\n parseResponse: (res) =>\n res\n .clone()\n .json()\n .catch(() => res.text())\n .then((data) => data || null),\n\n parseRejected: async (res, options) =>\n new ResponseError(\n res,\n await fallbackOptions.parseResponse(res, options),\n options,\n ),\n\n // TODO: find a lighter way to do this with about the same amount of code\n serializeParams: (params) =>\n // JSON.parse(JSON.stringify(params)) recursively transforms Dates to ISO strings and strips undefined\n new URLSearchParams(\n JSON.parse(JSON.stringify(params)) as Record<string, string>,\n ).toString(),\n\n serializeBody: (body: any) =>\n isJsonifiable(body) ? JSON.stringify(body) : body,\n\n reject: (response) => !response.ok,\n}\n","import type {\n ResolvedOptions,\n FetcherOptions,\n DefaultOptions,\n BaseFetchFn,\n Interceptors,\n FallbackOptions,\n} from './types'\nimport { fallbackOptions } from './fallback-options'\nimport {\n resolveParams,\n isJsonifiable,\n mergeHeaders,\n omit,\n emptyOptions,\n getUrl,\n stripUndefined,\n mergeSignal,\n} from './utils'\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\n\nexport let interceptors: Interceptors = ['onRequest', 'onSuccess', 'onError']\n\nexport let resolveOptions = <\n TFetchFn extends BaseFetchFn,\n TParsedData,\n TSchema extends StandardSchemaV1,\n TRawBody,\n>(\n input: Parameters<TFetchFn>[0], // fetch 1st arg\n defaultOptions: DefaultOptions<TFetchFn, any, any> = emptyOptions,\n fetcherOpts: FetcherOptions<\n TFetchFn,\n TSchema,\n TParsedData,\n TRawBody\n > = emptyOptions,\n): ResolvedOptions<TFetchFn, TSchema, TParsedData> => {\n // transform URL to string right away\n input = input?.href ?? input\n let mergedOptions = {\n ...(fallbackOptions as FallbackOptions<TFetchFn>),\n ...defaultOptions,\n ...fetcherOpts,\n }\n let rawBody = fetcherOpts.body\n let params = resolveParams(defaultOptions.params, input, fetcherOpts.params)\n\n let body: BodyInit | null | undefined =\n rawBody === null || rawBody === undefined\n ? (rawBody as null | undefined)\n : mergedOptions.serializeBody(rawBody)\n\n mergedOptions.parseResponseError &&\n renamingDeprecation('parseResponseError', 'parseRejected')\n mergedOptions.throwResponseError &&\n renamingDeprecation('throwResponseError', 'reject')\n\n return stripUndefined({\n // I have to cast as mergedOptions because the type breaks with omit\n ...(omit(mergedOptions, interceptors) as typeof mergedOptions),\n params,\n rawBody,\n body,\n signal: mergeSignal(mergedOptions.signal, mergedOptions.timeout),\n headers: mergeHeaders(\n isJsonifiable(rawBody) && typeof body === 'string'\n ? { 'content-type': 'application/json' }\n : {},\n defaultOptions.headers,\n fetcherOpts.headers,\n ),\n // throwResponseError will be renamed reject in v2.0\n reject:\n fetcherOpts.reject ??\n fetcherOpts.throwResponseError ??\n defaultOptions.reject ??\n defaultOptions.throwResponseError ??\n fallbackOptions.reject,\n // parseResponseError will be renamed parseRejected in v2.0\n parseRejected:\n fetcherOpts.parseRejected ??\n fetcherOpts.parseResponseError ??\n defaultOptions.parseRejected ??\n defaultOptions.parseResponseError ??\n fallbackOptions.parseRejected,\n input: getUrl(\n mergedOptions.baseUrl,\n input,\n mergedOptions.serializeParams(params),\n ),\n })\n}\n\nfunction renamingDeprecation(oldName: string, newName: string) {\n // eslint-disable-next-line no-console\n console.warn(\n `Deprecation warning: \\`${oldName}\\` will be renamed \\`${newName}\\` in v2.0`,\n )\n}\n","import type { StandardSchemaV1 } from '@standard-schema/spec'\nimport { resolveOptions } from './resolve-options'\nimport type {\n FetcherOptions,\n DefaultOptions,\n BaseFetchFn,\n FallbackOptions,\n} from './types'\nimport { emptyOptions, validate } from './utils'\n\n\n\nexport function up<\n TFetchFn extends BaseFetchFn,\n TDefaultParsedData = any,\n TDefaultRawBody = Parameters<FallbackOptions<any>['serializeBody']>[0],\n>(\n fetchFn: TFetchFn,\n getDefaultOptions: (\n input: Parameters<TFetchFn>[0],\n fetcherOpts: FetcherOptions<TFetchFn, any, any, any>,\n ctx?: Parameters<TFetchFn>[2],\n ) => DefaultOptions<TFetchFn, TDefaultParsedData, TDefaultRawBody> = () =>\n emptyOptions,\n) {\n return async <\n TParsedData = TDefaultParsedData,\n TSchema extends StandardSchemaV1<\n TParsedData,\n any\n > = StandardSchemaV1<TParsedData>,\n TRawBody = TDefaultRawBody,\n >(\n input: Parameters<TFetchFn>[0],\n fetcherOpts: FetcherOptions<\n TFetchFn,\n TSchema,\n TParsedData,\n TRawBody\n > = emptyOptions,\n ctx?: Parameters<TFetchFn>[2],\n ) => {\n let defaultOpts = getDefaultOptions(input, fetcherOpts, ctx)\n let options = resolveOptions(input, defaultOpts, fetcherOpts)\n\n await defaultOpts?.onRequest?.(options)\n // if (defaultOpts.onRequest && isPromise(defaultOpts.onRequest)) {\n // } else {\n // defaultOpts.onRequest?.(options)\n // }\n return fetchFn(options.input, options, ctx)\n .catch((error) => {\n defaultOpts.onError?.(error, options)\n throw error\n })\n .then(async (response: Response) => {\n if (!(await options.reject(response))) {\n let parsed: Awaited<TParsedData>\n try {\n parsed = await options.parseResponse(response, options)\n } catch (error: any) {\n defaultOpts.onError?.(error, options)\n throw error\n }\n let data: Awaited<StandardSchemaV1.InferOutput<TSchema>>\n try {\n data = options.schema\n ? await validate(options.schema, parsed)\n : parsed\n } catch (error: any) {\n defaultOpts.onError?.(error, options)\n throw error\n }\n defaultOpts.onSuccess?.(data, options)\n return data\n } else {\n let respError: any\n try {\n respError = await options.parseRejected(response, options)\n } catch (error: any) {\n defaultOpts.onError?.(error, options)\n throw error\n }\n defaultOpts.onError?.(respError, options)\n throw respError\n }\n })\n }\n}\n"],"mappings":"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","names":["src_exports","__export","ResponseError","isJsonifiable","isResponseError","isValidationError","up","__toCommonJS","ResponseError","res","data","options","isResponseError","error","ValidationError","result","data","isValidationError","error","mergeHeaders","headerInits","res","init","value","key","mergeSignal","signal","timeout","resolveParams","defaultParams","input","fetcherParams","stripUndefined","omit","obj","keys","copy","isJsonifiable","emptyOptions","getUrl","base","queryString","url","validate","schema","data","result","ValidationError","fallbackOptions","res","data","options","ResponseError","params","body","isJsonifiable","response","interceptors","resolveOptions","input","defaultOptions","emptyOptions","fetcherOpts","mergedOptions","fallbackOptions","rawBody","params","resolveParams","body","renamingDeprecation","stripUndefined","omit","mergeSignal","mergeHeaders","isJsonifiable","getUrl","oldName","newName","up","fetchFn","getDefaultOptions","emptyOptions","input","fetcherOpts","ctx","defaultOpts","options","resolveOptions","error","response","respError","parsed","data","validate"]}