UNPKG

shelving

Version:

Toolkit for using data in JavaScript.

135 lines (134 loc) 5.18 kB
import { RequiredError } from "../error/RequiredError.js"; import { ValueError } from "../error/ValueError.js"; import { getDictionaryItems, isDictionary } from "./dictionary.js"; import { getString, isString } from "./string.js"; export const ImmutableURI = URL; /** Is an unknown value a URI object? */ export function isURI(value) { return value instanceof ImmutableURI; } /** 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. * - Only inputs that already encode a complete URI succeed — relative inputs return `undefined`. No implicit fallback to the document or window URL. * - To resolve a relative ref against a base, use `getBasedURI()` from `url.ts`. */ export function getURI(possible) { if (!possible) return; if (isURI(possible)) return possible; try { return new URL(possible); } catch { // } } /** 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; } /** * 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(params, caller = getURIParams) { if (params instanceof URLSearchParams) { yield* params; } else if (isString(params) || params instanceof URL) { yield* requireURI(params, caller).searchParams; } else { const done = []; for (const [key, value] of getDictionaryItems(params)) { 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(params, caller = getURIParams) { const output = {}; for (const [key, str] of getURIEntries(params, caller)) output[key] = str; return output; } /** Get a single named param from a URI. */ export function getURIParam(params, key) { if (params instanceof URLSearchParams) return params.get(key) || undefined; if (isDictionary(params)) return getString(params[key]); return getURIParams(params)[key]; } /** Get a single named param from a URI. */ export function requireURIParam(params, key, caller = requireURIParam) { const value = getURIParam(params, key); if (value === undefined) throw new RequiredError(`URI param "${key}" is required`, { received: params, caller }); return value; } export function withURIParam(uri, key, value, caller = withURIParam) { const input = requireURI(uri, caller); if (value === undefined) return input; // Ignore undefined. const output = new ImmutableURI(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(uri, params, caller = withURIParams) { const input = requireURI(uri, caller); if (!params) return input; const output = new ImmutableURI(input); for (const [key, str] of getURIEntries(params, caller)) output.searchParams.set(key, str); return input.href === output.href ? input : output; } export function omitURIParams(uri, ...keys) { const input = requireURI(uri, omitURIParams); if (!keys.length) return input; const output = new ImmutableURI(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(uri, caller = clearURIParams) { const input = requireURI(uri, caller); if (!input.search.length) return input; const output = new URL(input); output.search = ""; return output; } /** Valid HTTP schemes for a URI. */ export const HTTP_SCHEMES = ["http:", "https:"];