@cardbrother/up-fetch
Version:
Advanced fetch client builder for typescript.
133 lines (118 loc) • 4.18 kB
text/typescript
import type { StandardSchemaV1 } from '@standard-schema/spec'
import type {
Params,
RawHeaders,
JsonifiableObject,
JsonifiableArray,
} from './types'
import { ValidationError } from './validation-error'
export let mergeHeaders = (...headerInits: (RawHeaders | undefined)[]) => {
let res: Record<string, string> = {}
headerInits.forEach((init) => {
// casting `init` to `HeadersInit` because `Record<string, any>` is
// properly transformed to `Record<string,string>` by `new Headers(init)`
new Headers(init as HeadersInit | undefined).forEach((value, key) => {
if (value === 'null' || value === 'undefined') {
delete res[key]
} else {
res[key] = value
}
})
})
return res
}
export let mergeSignal = (
signal: AbortSignal | undefined,
timeout: number | undefined,
): AbortSignal | undefined =>
// if AbortSignal.any is not supported
// AbortSignal.timeout is not supported either
'any' in AbortSignal
? AbortSignal.any(
[signal, timeout && AbortSignal.timeout(timeout)].filter(
Boolean,
) as AbortSignal[],
)
: signal
export let resolveParams = (
defaultParams: Params | undefined,
input: unknown,
fetcherParams: Params | undefined,
): Params =>
typeof input !== 'string'
? {} // an input of type Request cannot use the "params" option
: stripUndefined({
// Removing the 'url.searchParams.keys()' from the defaultParams
// but not from the 'fetcherParams'. The user is responsible for not
// specifying the params in both the "input" and the fetcher "params" option.
...omit(defaultParams, [
...new URL(input, 'http://a').searchParams.keys(),
]),
...fetcherParams,
})
type KeyOf<O> = O extends unknown ? keyof O : never
export type DistributiveOmit<
TObject extends object,
TKey extends KeyOf<TObject> | (string & {}),
> = TObject extends unknown ? Omit<TObject, TKey> : never
export type MaybePromise<T> = T | Promise<T>
export let omit = <O extends object, K extends KeyOf<O> | (string & {})>(
obj?: O,
keys: K[] | readonly K[] = [],
): DistributiveOmit<O, K> => {
let copy = { ...obj } as DistributiveOmit<O, K>
for (let key in copy) {
if (keys.includes(key as any as K)) delete copy[key]
}
return copy
}
export let stripUndefined = <O extends object>(obj?: O): O => {
let copy = { ...obj } as O
for (let key in copy) {
if (copy[key] === undefined) delete copy[key]
}
return copy
}
export let isJsonifiable = (
value: any,
): value is JsonifiableObject | JsonifiableArray => {
// bun FormData has a toJSON method
if (!value || typeof value !== 'object' || value instanceof FormData)
return false
return (
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
value?.constructor?.name === 'Object' ||
Array.isArray(value) ||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
typeof value?.toJSON === 'function'
)
}
export let emptyOptions: any = {}
export function getUrl(
base: string | undefined = '',
input: unknown,
queryString: string,
): any {
if (typeof input !== 'string') return input
let url = /^https?:\/\//.test(input)
? input
: !base || !input
? base + input
: base.replace(/\/$/, '') + '/' + input.replace(/^\//, '')
if (queryString) {
url += (url.includes('?') ? '&' : '?') + queryString.replace(/^\?/, '')
}
return url
}
export async function validate<TSchema extends StandardSchemaV1>(
schema: TSchema,
data: StandardSchemaV1.InferInput<TSchema>,
): Promise<StandardSchemaV1.InferOutput<TSchema>> {
let result = await schema['~standard'].validate(data)
if (result.issues) throw new ValidationError(result, data)
return result.value
}
export function isPromise(f: any): boolean {
const fnContent = f.toString();
return Object.prototype.toString.call(f) === '[object AsyncFunction]' || fnContent.includes("return _regenerator.default.async(function")
}