@rzl-zone/utils-js
Version:
A modern, lightweight set of JavaScript utility functions with TypeScript support for everyday development, crafted to enhance code readability and maintainability.
247 lines (242 loc) • 8.62 kB
JavaScript
/*!
* ====================================================
* Rzl Utils-JS.
* ----------------------------------------------------
* Version: 3.11.0.
* Author: Rizalvin Dwiky.
* Repository: https://github.com/rzl-zone/utils-js.
* ====================================================
*/
import { isValidDomain } from './chunk-5WIEDF2J.js';
import { removeSpaces } from './chunk-HNBRGN4R.js';
import { isEmptyString } from './chunk-ULQPCIA2.js';
import { safeStableStringify } from './chunk-AXDYWO67.js';
import { assertIsPlainObject, isNonEmptyString, getPreciseType, assertIsBoolean, isUndefined, isSet, isArray, isNil, isString, isNull, isError, hasOwnProp, isBoolean, isFunction } from './chunk-MSUW5VHZ.js';
var NormalizePathnameError = class _NormalizePathnameError extends Error {
/** * ***The original error that triggered this normalization failure.***
*
* **Always available for backward compatibility.**
*/
originalError;
constructor(message, originalError) {
super(message, isFunction(Error) ? { cause: originalError } : void 0);
this.name = "NormalizePathnameError";
this.originalError = originalError;
if (isFunction(Error.captureStackTrace)) {
Error.captureStackTrace(this, _NormalizePathnameError);
} else {
this.stack = new Error(message).stack;
}
}
/** * ***Safe JSON representation for logging or IPC.*** */
toJSON() {
return {
name: this.name,
message: this.message,
stack: this.stack,
originalError: {
name: this.originalError.name,
message: this.originalError.message,
stack: this.originalError.stack
}
};
}
};
function normalizePathname(pathname, options = {
defaultPath: "/",
keepNullable: false
}) {
assertIsPlainObject(options, {
message({ currentType, validType }) {
return `Second parameter (\`options\`) must be of type \`${validType}\`, but received: \`${currentType}\`.`;
}
});
const {
defaultPath = "/",
keepNullable = false,
keepTrailingSlash = false,
localhostDomain = false,
ignoreDomainExtensions = void 0
} = options;
if (!isNonEmptyString(defaultPath)) {
throw new TypeError(
`Parameter \`defaultPath\` property of the \`options\` (second parameter) must be of type \`string\` and not empty-string, but received: \`${getPreciseType(
defaultPath
)}\`, with value: \`${safeStableStringify(defaultPath, {
keepUndefined: true
})}\`.`
);
}
assertIsBoolean(keepNullable, {
message({ currentType, validType }) {
return `Parameter \`keepNullable\` property of the \`options\` (second parameter) must be of type \`${validType}\`, but received: \`${currentType}\`.`;
}
});
assertIsBoolean(keepTrailingSlash, {
message({ currentType, validType }) {
return `Parameter \`keepTrailingSlash\` property of the \`options\` (second parameter) must be of type \`${validType}\`, but received: \`${currentType}\`.`;
}
});
assertIsBoolean(localhostDomain, {
message({ currentType, validType }) {
return `Parameter \`localhostDomain\` property of the \`options\` (second parameter) must be of type \`${validType}\`, but received: \`${currentType}\`.`;
}
});
let ignoreDomainExtsSet;
if (!isUndefined(ignoreDomainExtensions)) {
if (!isSet(ignoreDomainExtensions) && !isArray(ignoreDomainExtensions)) {
throw new TypeError(
`Parameter \`ignoreDomainExtensions\` must be of type a \`Set<string>\` or \`string[]\`, but received: \`${getPreciseType(
ignoreDomainExtensions
)}\`.`
);
}
ignoreDomainExtsSet = isSet(ignoreDomainExtensions) ? ignoreDomainExtensions : new Set(ignoreDomainExtensions);
let idx = 0;
for (const ext of ignoreDomainExtsSet) {
if (!isNonEmptyString(ext)) {
throw new TypeError(
`Parameter \`ignoreDomainExtensions[${idx}]\` must be a \`string\` and \`non-empty string\`, but received: \`${safeStableStringify(
ext,
{ keepUndefined: true }
)}\`.`
);
}
if (!ext.startsWith(".")) {
throw new TypeError(
`Parameter \`ignoreDomainExtensions[${idx}]\` must start with a dot (.), but received: ${safeStableStringify(
ext,
{ keepUndefined: true }
)}`
);
}
idx++;
}
}
try {
if (keepNullable && (isNil(pathname) || !isString(pathname))) {
if (isNull(pathname)) return null;
return void 0;
}
let currentPathName = isNonEmptyString(pathname) ? pathname : defaultPath;
currentPathName = removeSpaces(currentPathName, { trimOnly: true }).replace(
/\s+/g,
""
);
currentPathName = stripLeadingDomain(currentPathName, {
keepTrailingSlash,
localhostDomain,
ignoreDomainExtensions: ignoreDomainExtsSet
});
let _pathName = currentPathName;
let search = "";
let hash = "";
const searchIndex = currentPathName.indexOf("?");
const hashIndex = currentPathName.indexOf("#");
if (searchIndex !== -1) {
search = currentPathName.slice(
searchIndex,
hashIndex !== -1 ? hashIndex : void 0
);
}
if (hashIndex !== -1) {
hash = currentPathName.slice(hashIndex);
}
const endIndex = Math.min(
searchIndex !== -1 ? searchIndex : currentPathName.length,
hashIndex !== -1 ? hashIndex : currentPathName.length
);
_pathName = currentPathName.slice(0, endIndex);
_pathName = "/" + _pathName.replace(/^\/+/, "").replace(/\/{2,}/g, "/");
if (!keepTrailingSlash && _pathName !== "/") {
_pathName = _pathName.replace(/\/+$/, "");
}
_pathName = decodeUnicodeSequences(_pathName);
search = decodeUnicodeSequences(search);
hash = decodeUnicodeSequences(hash);
return _pathName + search + hash;
} catch (error) {
throwError(error);
}
}
var decodeUnicodeSequences = (str) => {
return str.replace(/(?:%(?:[0-9A-F]{2})){2,}/gi, (match) => {
try {
const decoded = decodeURIComponent(match);
if (/^[\u0000-\u007F]+$/.test(decoded)) return match;
return decoded;
} catch {
return match;
}
});
};
var stripLeadingDomain = (path, options) => {
let currentPath = path;
const { ignoreDomainExtensions, localhostDomain } = options;
if (/^https?:\/\//i.test(currentPath)) {
try {
const url = new URL(currentPath);
currentPath = url.pathname.replace(/^\/+/, "").replace(/\/{2,}/g, "/") + url.search + url.hash;
return ensureLeadingSlash(currentPath);
} catch (error) {
throwError(error);
}
}
if (currentPath.startsWith("/")) {
currentPath = currentPath.replace(/\/{2,}/g, "/").slice(1);
}
const segments = currentPath.split("/");
const firstPart = segments[0];
const domainPart = firstPart.split(":")[0];
const isDomain = isValidDomain(domainPart, {
subdomain: true,
allowUnicode: true,
wildcard: true,
allowLocalhost: localhostDomain,
allowPort: true,
allowProtocol: true,
topLevel: false
});
let hasIgnoredExtension = false;
if (ignoreDomainExtensions) {
for (const ext of ignoreDomainExtensions) {
if (firstPart.endsWith(ext)) {
hasIgnoredExtension = true;
break;
}
}
}
if (isDomain && !hasIgnoredExtension) {
segments.shift();
}
return ensureLeadingSlash(segments.join("/"));
};
var ensureLeadingSlash = (path) => {
if (!path.startsWith("/")) path = "/" + path;
return path;
};
var throwError = (error) => {
const err = isError(error) ? error : new Error("Unknown error from function `normalizePathname()`.");
throw new NormalizePathnameError(
`Failed to normalize pathname in function \`normalizePathname()\`: ${err.message}`,
err
);
};
var formatEnvPort = (envVar, options = {}) => {
if (!isNonEmptyString(envVar)) return "";
assertIsPlainObject(options, {
message: ({ currentType, validType }) => `Second parameter (\`options\`) must be of type \`${validType}\`, but received: \`${currentType}\`.`
});
const prefixColon = hasOwnProp(options, "prefixColon") ? options.prefixColon : false;
if (!isBoolean(prefixColon)) {
throw new TypeError(
`Parameter \`prefixColon\` property of the \`options\` (second parameter) must be of type \`boolean\`, but received: \`${getPreciseType(
prefixColon
)}\`.`
);
}
const digitsOnly = envVar.replace(/\D+/g, "");
if (isEmptyString(digitsOnly)) return "";
return prefixColon ? `:${digitsOnly}` : digitsOnly;
};
export { formatEnvPort, normalizePathname };