UNPKG

shelving

Version:

Toolkit for using data in JavaScript.

137 lines (136 loc) 5.38 kB
import { RequiredError } from "../error/RequiredError.js"; import { ValueError } from "../error/ValueError.js"; import { getDictionaryItems, isDictionary } from "./dictionary.js"; import { notNullish } from "./null.js"; import { getString, isString } from "./string.js"; export const URI = globalThis.URL; /** Is an unknown value a URI object? */ export function isURI(value) { return value instanceof URI; } /** Assert that an unknown value is a URI object. */ export function assertURI(value, caller = assertURI) { if (!isURI(value)) throw new RequiredError("Invalid URI", { received: value, caller }); } /** Convert a possible URI to a URI, or return `undefined` if conversion fails. */ export function getURI(possible) { if (notNullish(possible)) { if (isURI(possible)) return possible; try { return new globalThis.URL(possible, _BASE); } catch { return undefined; } } } const _BASE = typeof document === "object" ? document.baseURI : undefined; /** Convert a possible URI to a URI, or throw `RequiredError` if conversion fails. */ export function requireURI(possible, caller = requireURI) { const url = getURI(possible); assertURI(url, caller); return url; } /** Convert a possible URI to a URI string, or return `undefined` if conversion fails. */ export function getURIString(possible) { return getURI(possible)?.href; } /** Convert a possible URI to a URI string, or throw `RequiredError` if conversion fails. */ export function requireURIString(possible, caller = requireURIString) { return requireURI(possible, caller).href; } /** * Get a set of entries for a set of possible URI params. *- Any params with `undefined` value will be ignored. * * Note: Not as simple as just converting with `Object.fromEntries()`: * 1. When `URLSearchParams` contains multiple values for the same key, calling `params.get()` will return the _first_ value. * 2. So when converting this to a simple data object, only one value per key can be represented, but it needs to be the _first_ one. * 3. Since we're looping through anyway, we also take the time to convert values to strings, so we can accept a wider range of input types. */ function* getURIEntries(input, caller = getURIParams) { if (input instanceof URLSearchParams) { yield* input; } else if (isString(input) || input instanceof globalThis.URL) { yield* requireURI(input, caller).searchParams; } else { const done = []; for (const [key, value] of getDictionaryItems(input)) { if (value === undefined) continue; // Skip undefined. if (done.includes(key)) continue; done.push(key); const str = getString(value); if (str === undefined) throw new ValueError(`URI param "${key}" must be string`, { received: value, caller }); yield [key, str]; } } } /** * Get a set of params for a URI as a dictionary. * - Any params with `undefined` value will be ignored. */ export function getURIParams(input, caller = getURIParams) { const output = {}; for (const [key, str] of getURIEntries(input, caller)) output[key] = str; return output; } /** Get a single named param from a URI. */ export function getURIParam(input, key) { if (input instanceof URLSearchParams) return input.get(key) || undefined; if (isDictionary(input)) return getString(input[key]); return getURIParams(input)[key]; } /** Get a single named param from a URI. */ export function requireURIParam(input, key, caller = requireURIParam) { const value = getURIParam(input, key); if (value === undefined) throw new RequiredError(`URI param "${key}" is required`, { received: input, caller }); return value; } export function withURIParam(url, key, value, caller = withURIParam) { const input = requireURI(url, caller); if (value === undefined) return input; // Ignore undefined. const output = new URI(input); const str = getString(value); if (str === undefined) throw new ValueError(`URI param "${key}" must be string`, { received: value, caller }); output.searchParams.set(key, str); return input.href === output.href ? input : output; } export function withURIParams(url, params, caller = withURIParams) { const input = requireURI(url, caller); const output = new URI(input); for (const [key, str] of getURIEntries(params, caller)) output.searchParams.set(key, str); return input.href === output.href ? input : output; } export function omitURIParams(url, ...keys) { const input = requireURI(url, omitURIParams); const output = new URI(input); for (const key of keys) output.searchParams.delete(key); return input.href === output.href ? input : output; } /** Return a URI without a param (or same URI if no changes were made). */ export const omitURIParam = omitURIParams; export function clearURIParams(url, caller = clearURIParams) { const input = requireURI(url, caller); if (!input.search.length) return input; const output = new URI(input); output.search = ""; return output; } /** Valid HTTP schemes for a URI. */ export const HTTP_SCHEMES = ["http:", "https:"];