shelving
Version:
Toolkit for using data in JavaScript.
135 lines (134 loc) • 5.18 kB
JavaScript
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:"];