UNPKG

ky

Version:

Tiny and elegant HTTP client based on the Fetch API

157 lines 6.31 kB
import { supportsAbortSignal } from '../core/constants.js'; import { isObject } from './is.js'; export const validateAndMerge = (...sources) => { for (const source of sources) { if ((!isObject(source) || Array.isArray(source)) && source !== undefined) { throw new TypeError('The `options` argument must be an object'); } } return deepMerge({}, ...sources); }; export const mergeHeaders = (source1 = {}, source2 = {}) => { const result = new globalThis.Headers(source1); const isHeadersInstance = source2 instanceof globalThis.Headers; const source = new globalThis.Headers(source2); for (const [key, value] of source.entries()) { if ((isHeadersInstance && value === 'undefined') || value === undefined) { result.delete(key); } else { result.set(key, value); } } return result; }; function newHookValue(original, incoming, property) { return (Object.hasOwn(incoming, property) && incoming[property] === undefined) ? [] : deepMerge(original[property] ?? [], incoming[property] ?? []); } export const mergeHooks = (original = {}, incoming = {}) => ({ beforeRequest: newHookValue(original, incoming, 'beforeRequest'), beforeRetry: newHookValue(original, incoming, 'beforeRetry'), afterResponse: newHookValue(original, incoming, 'afterResponse'), beforeError: newHookValue(original, incoming, 'beforeError'), }); const appendSearchParameters = (target, source) => { const result = new URLSearchParams(); for (const input of [target, source]) { if (input === undefined) { continue; } if (input instanceof URLSearchParams) { for (const [key, value] of input.entries()) { result.append(key, value); } } else if (Array.isArray(input)) { for (const pair of input) { if (!Array.isArray(pair) || pair.length !== 2) { throw new TypeError('Array search parameters must be provided in [[key, value], ...] format'); } result.append(String(pair[0]), String(pair[1])); } } else if (isObject(input)) { for (const [key, value] of Object.entries(input)) { if (value !== undefined) { result.append(key, String(value)); } } } else { // String const parameters = new URLSearchParams(input); for (const [key, value] of parameters.entries()) { result.append(key, value); } } } return result; }; // TODO: Make this strongly-typed (no `any`). export const deepMerge = (...sources) => { let returnValue = {}; let headers = {}; let hooks = {}; let searchParameters; const signals = []; for (const source of sources) { if (Array.isArray(source)) { if (!Array.isArray(returnValue)) { returnValue = []; } returnValue = [...returnValue, ...source]; } else if (isObject(source)) { for (let [key, value] of Object.entries(source)) { // Special handling for AbortSignal instances if (key === 'signal' && value instanceof globalThis.AbortSignal) { signals.push(value); continue; } // Special handling for context - shallow merge only if (key === 'context') { if (value !== undefined && value !== null && (!isObject(value) || Array.isArray(value))) { throw new TypeError('The `context` option must be an object'); } // Shallow merge: always create a new object to prevent mutation bugs returnValue = { ...returnValue, context: (value === undefined || value === null) ? {} : { ...returnValue.context, ...value }, }; continue; } // Special handling for searchParams if (key === 'searchParams') { if (value === undefined || value === null) { // Explicit undefined or null removes searchParams searchParameters = undefined; } else { // First source: keep as-is to preserve type (string/object/URLSearchParams) // Subsequent sources: merge and convert to URLSearchParams searchParameters = searchParameters === undefined ? value : appendSearchParameters(searchParameters, value); } continue; } if (isObject(value) && key in returnValue) { value = deepMerge(returnValue[key], value); } returnValue = { ...returnValue, [key]: value }; } if (isObject(source.hooks)) { hooks = mergeHooks(hooks, source.hooks); returnValue.hooks = hooks; } if (isObject(source.headers)) { headers = mergeHeaders(headers, source.headers); returnValue.headers = headers; } } } if (searchParameters !== undefined) { returnValue.searchParams = searchParameters; } if (signals.length > 0) { if (signals.length === 1) { returnValue.signal = signals[0]; } else if (supportsAbortSignal) { returnValue.signal = AbortSignal.any(signals); } else { // When AbortSignal.any is not available, use the last signal // This maintains the previous behavior before signal merging was added // This can be remove when the `supportsAbortSignal` check is removed.` returnValue.signal = signals.at(-1); } } if (returnValue.context === undefined) { returnValue.context = {}; } return returnValue; }; //# sourceMappingURL=merge.js.map