UNPKG

universal-common

Version:

Library that provides useful missing base class library functionality.

204 lines (178 loc) 5.84 kB
import StringBuilder from "./StringBuilder.js"; /** * A builder class for constructing relative URIs. * * Relative URIs can be one of three types: * 1. Scheme-relative (starts with "//") - inherits protocol from current page * 2. Root-relative (starts with "/") - relative to domain root * 3. Current-path-relative (no prefix) - relative to current path * * This class follows the builder pattern to allow fluent method chaining. */ export default class RelativeUriBuilder { /** * Type constant for scheme-relative URIs (e.g., "//example.com/path") * These URIs inherit the protocol (http/https) from the current page. * @type {string} */ static get TYPE_SCHEME() { return "//"; } /** * Type constant for root-relative URIs (e.g., "/path/to/resource") * These URIs are relative to the domain root. * @type {string} */ static get TYPE_ROOT() { return "/"; } /** * Type constant for current-path-relative URIs (e.g., "resource" or "subdir/resource") * These URIs are relative to the current path. * @type {string} */ static get TYPE_CURRENT() { return ""; } #type; #pathSegments = []; #queryParameters = new URLSearchParams(); /** * Creates a new RelativeUriBuilder instance. * * @param {string} type - The type of relative URI to build. * Use the static TYPE_* constants for valid values. * @example * // Create a root-relative URI builder * const builder = new RelativeUriBuilder(RelativeUriBuilder.TYPE_ROOT); */ constructor(type) { this.#type = type; } /** * Gets the path part of the URI. * * @returns {string} The path as a string with trailing slash */ get path() { let stringBuilder = new StringBuilder(); for (let segment of this.#pathSegments) { stringBuilder.append(`${segment}/`); } return stringBuilder.toString(); } /** * Gets the query string part of the URI. * * @returns {string} The query string without the leading '?' */ get query() { return this.#queryParameters.toString(); } /** * Gets a copy of the path segments array. * * @returns {Array<string>} Array of path segments */ get segments() { return [...this.#pathSegments]; } /** * Gets the type of relative URI being built. * * @returns {string} The URI type (one of the TYPE_* constants) */ get type() { return this.#type; } /** * Sets the type of relative URI being built. * * @param {string} value - The URI type (should be one of the TYPE_* constants) */ set type(value) { this.#type = value; } /** * Adds a single path segment. * * @param {string} value - The path segment to add * @returns {RelativeUriBuilder} This builder instance for method chaining */ addSegment(value) { this.#pathSegments.push(value); return this; } /** * Adds multiple path segments. * * @param {...string} arguments - Path segments to add * @returns {RelativeUriBuilder} This builder instance for method chaining * * DIFFERENCE FROM UriBuilder: This implementation doesn't handle array arguments, * unlike the similar method in UriBuilder. */ addSegments() { const segments = Array.isArray(arguments[0]) && arguments.length === 1 ? arguments[0] // Use the array directly : arguments; // Use the arguments object for (let segment of segments) { this.addSegment(segment); } return this; } /** * Adds a query parameter. * * @param {string} name - The parameter name * @param {string} value - The parameter value * @returns {RelativeUriBuilder} This builder instance for method chaining */ addQuery(name, value) { this.#queryParameters.append(name, value); return this; } /** * Adds multiple query parameters from an object. * * @param {Object|Map} queries - Object containing name-value pairs * @returns {RelativeUriBuilder} This builder instance for method chaining */ addQueries(queries) { if (queries instanceof Map) { for (const [key, value] of queries) { this.addQuery(key, value); } } else { for (const key in queries) { this.addQuery(key, queries[key]); } } return this; } /** * Builds and returns the complete relative URI string. * * @returns {string} The complete relative URI */ get uri() { // Determine the prefix based on the URI type let prefix; switch (this.type) { case RelativeUriBuilder.TYPE_SCHEME: prefix = "//"; break; case RelativeUriBuilder.TYPE_ROOT: prefix = "/"; break; default: prefix = ""; break; } // Build the URI let stringBuilder = new StringBuilder(prefix); // Add path (removing the trailing slash) if (this.path) { stringBuilder.append(this.path.slice(0, -1)); } // Add query string if present if (this.query) { stringBuilder.append(`?${this.query}`); } return stringBuilder.toString(); } }