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