build-url-ts
Version:
A small library that builds a URL given its components
153 lines (151 loc) • 6.08 kB
TypeScript
/**
* `build-url-ts` — a small, fast, zero-dependency library for composing URLs
* from their parts (path, query string, and hash fragment).
*
* It runs anywhere JavaScript does (Node.js, Bun, Deno, edge runtimes, and
* browsers) and ships both ESM and CommonJS builds with full type definitions.
*
* @packageDocumentation
* @example
* ```ts
* import { buildUrl } from 'build-url-ts';
*
* buildUrl('https://api.example.com', {
* path: 'users/123',
* queryParams: { tab: 'profile', limit: 10 },
* hash: 'summary',
* });
* // → https://api.example.com/users/123?tab=profile&limit=10#summary
* ```
*/
/**
* A value accepted for a single query parameter.
*
* - `string` / `number` / `boolean` are stringified as-is.
* - `null` becomes an empty value (`key=`).
* - `undefined` is omitted entirely.
* - An array is rendered as a comma-separated list by default, or as repeated /
* indexed keys via {@link IBuildUrlOptions.disableCSV}.
* - A `Date` is serialized with `Date.prototype.toString()`.
* - Any other `object` is serialized with `JSON.stringify()`.
*/
type QueryParamValue = null | undefined | string | number | boolean | (string | number | boolean | null | undefined)[] | Date | object;
/** A map of query parameter names to {@link QueryParamValue}s. */
type IQueryParams = Record<string, QueryParamValue>;
/**
* How array query parameters are serialized when CSV joining is disabled.
*
* - `'array'` → `key[]=a&key[]=b`
* - `'order_asc'` → `key[0]=a&key[1]=b`
* - `'order_desc'` → `key[1]=a&key[0]=b`
*
* The boolean `true` (see {@link IBuildUrlOptions.disableCSV}) produces repeated
* keys without brackets: `key=a&key=b`.
*/
type IDisableCsvType = 'array' | 'order_asc' | 'order_desc';
/** Options describing the parts of the URL to build. */
interface IBuildUrlOptions {
/**
* A single path segment appended to the URL. Leading, trailing, and duplicate
* slashes are normalized.
*
* @example 'about/me' // → /about/me
*/
path?: string | number;
/**
* Multiple path segments appended in order. Use this instead of (or in
* addition to) {@link IBuildUrlOptions.path}; when both are given, `path` is
* applied first, then each entry of `paths`.
*
* @example ['about', '/my/', '/cat'] // → /about/my/cat
*/
paths?: (string | number)[];
/** Lowercase the generated path, query string, and hash. Defaults to `false`. */
lowerCase?: boolean;
/** Query parameters to append, merged on top of any already present on the URL. */
queryParams?: IQueryParams;
/**
* Control how array query parameters are rendered. `false`/omitted joins them
* into a comma-separated list; `true` repeats the key; a {@link IDisableCsvType}
* selects a bracketed format.
*/
disableCSV?: boolean | IDisableCsvType;
/** Hash/fragment identifier to append (without the leading `#`). */
hash?: string | number;
}
/**
* Builds a query string (including the leading `?`) from a parameters object.
*
* @param queryParams - The parameters to serialize.
* @param lowerCase - Lowercase keys and values. Defaults to `false`.
* @param disableCSV - How to render array values. See {@link IDisableCsvType}.
* @param useCustomEncoding - Use {@link customEncodeURIComponent} (escapes `'`
* and `` ` ``) instead of plain `encodeURIComponent`. Defaults to `true`.
* {@link buildUrl} passes `false` because it encodes values itself.
* @returns The query string (e.g. `?foo=bar&bar=baz`), or `''` when empty.
*
* @example
* ```ts
* buildQueryString({ foo: 'bar', ids: [1, 2, 3] });
* // → ?foo=bar&ids=1%2C2%2C3
* ```
*/
declare function buildQueryString(queryParams: IQueryParams, lowerCase?: boolean, disableCSV?: boolean | IDisableCsvType, useCustomEncoding?: boolean): string;
/**
* Appends a single path segment to a URL, normalizing slashes so there are no
* empty or doubled segments while any meaningful trailing slash is preserved.
*
* @param path - The segment to append.
* @param builtUrl - The URL built so far.
* @param lowerCase - Lowercase the segment. Defaults to `false`.
* @returns The URL with the segment appended.
*
* @example
* ```ts
* appendPath('users/123', 'https://api.example.com');
* // → https://api.example.com/users/123
* ```
*/
declare function appendPath(path: string | number, builtUrl: string, lowerCase?: boolean): string;
/**
* Builds a hash fragment (including the leading `#`) from a value.
*
* @param hash - The fragment text (without `#`).
* @param lowerCase - Lowercase the fragment. Defaults to `false`.
* @returns The hash fragment (e.g. `#section`), or `''` when empty.
*
* @example
* ```ts
* buildHash('Section-1', true); // → #section-1
* ```
*/
declare function buildHash(hash: string | number, lowerCase?: boolean): string;
/**
* Builds a complete URL from a base URL and/or a set of options.
*
* Query parameters and hash already present on the base URL are preserved and
* merged with the supplied options (options take precedence on conflicts).
*
* @param url - The base URL, or — when called with a single argument — the
* options object itself. `null`/`undefined` builds a relative URL.
* @param options - The parts to add. See {@link IBuildUrlOptions}.
* @returns The constructed URL string.
*
* @example
* ```ts
* // Base URL plus options
* buildUrl('https://example.com', { path: 'about', hash: 'team' });
* // → https://example.com/about#team
*
* // Options only (relative URL)
* buildUrl({ path: 'api/v2', queryParams: { format: 'json' } });
* // → /api/v2?format=json
*
* // Multiple path segments
* buildUrl('https://example.com', { paths: ['about', '/my/', '/cat'] });
* // → https://example.com/about/my/cat
* ```
*/
declare function buildUrl(url?: string | null | IBuildUrlOptions, options?: IBuildUrlOptions): string;
export { appendPath, buildHash, buildQueryString, buildUrl, buildUrl as default };
export type { IBuildUrlOptions, IDisableCsvType, IQueryParams, QueryParamValue };