UNPKG

@catbee/utils

Version:

A modular, production-grade utility toolkit for Node.js and TypeScript, designed for robust, scalable applications (including Express-based services). All utilities are tree-shakable and can be imported independently.

400 lines 15.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Env = exports.Environment = void 0; /* eslint-disable n/no-process-env */ const fs_1 = require("fs"); const path_1 = require("path"); /** * Enum representing valid application environments. */ var Environment; (function (Environment) { Environment["PRODUCTION"] = "production"; Environment["DEVELOPMENT"] = "development"; Environment["STAGING"] = "staging"; Environment["TESTING"] = "testing"; })(Environment || (exports.Environment = Environment = {})); /** * Utility class for accessing and managing environment variables. * Provides typed getters with fallback defaults and validation. */ class Env { /** * Checks if the current NODE_ENV is 'development'. * * @returns {boolean} `true` if NODE_ENV is 'development', else `false`. */ static isDev() { return (Env.get("NODE_ENV", Environment.DEVELOPMENT) === Environment.DEVELOPMENT); } /** * Checks if the current NODE_ENV is 'production'. * * @returns {boolean} `true` if NODE_ENV is 'production', else `false`. */ static isProd() { return Env.get("NODE_ENV") === Environment.PRODUCTION; } /** * Checks if the current NODE_ENV is 'testing'. * * @returns {boolean} `true` if NODE_ENV is 'testing', else `false`. */ static isTest() { return Env.get("NODE_ENV") === Environment.TESTING; } /** * Checks if the current NODE_ENV is 'staging'. * * @returns {boolean} `true` if NODE_ENV is 'staging', else `false`. */ static isStaging() { return Env.get("NODE_ENV") === Environment.STAGING; } /** * Sets an environment variable (only affects runtime memory). * * @param {string} key - The environment variable key. * @param {string} value - The value to set. */ static set(key, value) { process.env[key] = value; } /** * Returns all environment variables as an object. * * @returns {object} The current `process.env` object. */ static getAll() { return process.env; } /** * Retrieves a string environment variable with a fallback default. * * @param {string} key - The environment variable key. * @param {string} [defaultValue] - Value to return if the key is missing. * @returns {string | undefined} The env value or the fallback. */ static get(key, defaultValue) { var _a; return (_a = process.env[key]) !== null && _a !== void 0 ? _a : defaultValue; } /** * Retrieves a string environment variable and throws if it's missing. * * @param {string} key - The environment variable key. * @returns {string} The environment variable's value. * @throws {Error} If the variable is not defined. */ static getRequired(key) { const value = process.env[key]; if (value === undefined) { throw new Error(`Required env ${key} is missing`); } return value; } /** * Retrieves an environment variable as a number, or returns a default. * * @param {string} key - The environment variable key. * @param {number} defaultValue - Fallback number if key is not present. * @returns {number} Parsed numeric value or default. * @throws {Error} If the value is not a valid number. */ static getNumber(key, defaultValue) { var _a; const value = (_a = process.env[key]) !== null && _a !== void 0 ? _a : defaultValue; const numberValue = Number(value); if (isNaN(numberValue)) { throw new Error(`Env ${key} is not a number`); } return numberValue; } /** * Retrieves a required environment variable as a number. * * @param {string} key - The environment variable key. * @returns {number} Parsed number. * @throws {Error} If the value is missing or not a number. */ static getNumberRequired(key) { const value = process.env[key]; if (value === undefined) { throw new Error(`Required env ${key} is missing`); } const numberValue = Number(value); if (isNaN(numberValue)) { throw new Error(`Required env ${key} is not a number`); } return numberValue; } /** * Retrieves an environment variable as a boolean. * Accepts `true`, `1`, `yes`, `on` as true; `false`, `0`, `no`, `off` as false. * * @param {string} key - The environment variable key. * @param {boolean} [defaultValue=false] - Optional fallback value if key is missing. * @returns {boolean} Parsed boolean. * @throws {Error} If the value is not a recognized boolean string. */ static getBoolean(key, defaultValue = false) { var _a; const value = ((_a = process.env[key]) !== null && _a !== void 0 ? _a : defaultValue).toString().toLowerCase(); if (["true", "1", "yes", "on"].includes(value)) return true; if (["false", "0", "no", "off"].includes(value)) return false; throw new Error(`Env variable ${key} is not a boolean`); } /** * Retrieves a required environment variable as a boolean. * * @param {string} key - The environment variable key. * @returns {boolean} Parsed boolean value. * @throws {Error} If missing or invalid. */ static getBooleanRequired(key) { const value = process.env[key]; if (value === undefined) { throw new Error(`Required env ${key} is missing`); } return this.getBoolean(key); // reuse logic } /** * Parses a stringified JSON object from an environment variable. * * @typeParam T - The type to parse as (defaults to `object`). * @param {string} key - The environment variable key. * @param {T} defaultValue - Value to return if key is missing. * @returns {T} Parsed object or default. * @throws {Error} If the value is not valid JSON. */ static getJSON(key, defaultValue) { const v = process.env[key]; if (v !== undefined) { try { return JSON.parse(v); } catch (_a) { throw new Error(`Env variable ${key} is not a valid JSON string`); } } return defaultValue; } /** * Parses a comma-separated string as an array. * * @typeParam T - The item type (optional, defaults to string). * @param {string} key - The environment variable key. * @param {T[]} [defaultValue=[]] - Array to return if value is empty or missing. * @param {string} [splitter=','] - Delimiter to split on. * @returns {string[] | T[]} An array of strings. */ static getArray(key, defaultValue = [], splitter = ",") { const value = process.env[key]; if (!value || value.trim() === "") { return defaultValue; } return value .split(splitter) .map((item) => item.trim()) .filter((item) => item.length > 0); } /** * Retrieves an enum-like environment variable value, validating against allowed values. * * @typeParam T - The allowed value type (string literal types). * @param {string} key - The environment variable key. * @param {T[]} allowedValues - Array of accepted string values. * @param {T} [defaultValue] - Optional fallback value. * @returns {T} The validated environment value. * @throws {Error} If missing or invalid. */ static getEnum(key, allowedValues, defaultValue) { var _a; const value = (_a = process.env[key]) !== null && _a !== void 0 ? _a : defaultValue; if (!value || !allowedValues.includes(value)) { throw new Error(`Env ${key} must be one of ${allowedValues.join(", ")}. Received: ${value}`); } return value; } /** * Retrieves a URL environment variable and validates it. * * @param {string} key - The environment variable key. * @param {string} [defaultValue] - Optional fallback value. * @param {object} [options] - Validation options. * @param {string[]} [options.protocols] - Allowed protocols. * @param {boolean} [options.requireTld=true] - Whether TLD is required. * @returns {string} The validated URL. * @throws {Error} If URL is invalid. */ static getUrl(key, defaultValue, options = {}) { const value = Env.get(key, defaultValue); if (!value) { throw new Error(`Env ${key} is missing or empty`); } let url; try { url = new URL(value); } catch (_a) { throw new Error(`Env ${key} is not a valid URL`); } if (options.protocols && options.protocols.length > 0) { const protocol = url.protocol.replace(":", ""); if (!options.protocols.includes(protocol)) { throw new Error(`Env ${key} must use one of the protocols: ${options.protocols.join(", ")}`); } } if (options.requireTld === true) { // Check if hostname has a TLD (contains at least one dot and doesn't end with a dot) // localhost, 127.0.0.1, etc. don't have TLDs const hostname = url.hostname; if (!hostname.includes(".") || hostname === "localhost" || /^[\d.]+$/.test(hostname) || // IP address hostname.endsWith(".")) { throw new Error(`Env ${key} must have a valid host with TLD`); } } return value; } /** * Retrieves an email environment variable and validates it. * * @param {string} key - The environment variable key. * @param {string} [defaultValue] - Optional fallback value. * @returns {string} The validated email address. * @throws {Error} If email is invalid. */ static getEmail(key, defaultValue) { const value = Env.get(key, defaultValue); if (!value) { if (defaultValue === undefined) { throw new Error(`Email env ${key} is missing`); } return defaultValue; } // Simple email validation regex const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/; if (!emailRegex.test(value)) { throw new Error(`Env ${key} is not a valid email address`); } return value; } /** * Retrieves a path environment variable and validates it exists. * * @param {string} key - The environment variable key. * @param {string} [defaultValue] - Optional fallback value. * @param {object} [options] - Validation options. * @param {boolean} [options.mustExist=false] - Whether path must exist. * @param {boolean} [options.makeAbsolute=true] - Convert relative paths to absolute. * @returns {string} The validated path. * @throws {Error} If path is invalid. */ static getPath(key, defaultValue, options = {}) { const value = Env.get(key, defaultValue); if (!value) { if (defaultValue === undefined) { throw new Error(`Path env ${key} is missing`); } return defaultValue; } const path = options.makeAbsolute !== false && !(0, path_1.isAbsolute)(value) ? (0, path_1.resolve)(process.cwd(), value) : value; if (options.mustExist && !(0, fs_1.existsSync)(path)) { throw new Error(`Path in env ${key} does not exist: ${path}`); } return path; } /** * Retrieves a port environment variable and validates it. * * @param {string} key - The environment variable key. * @param {number} [defaultValue=3000] - Optional fallback value. * @returns {number} The validated port number. * @throws {Error} If port is invalid. */ static getPort(key, defaultValue = 3000) { const port = Env.getNumber(key, defaultValue); if (port < 0 || port > 65535) { throw new Error(`Env ${key} must be a valid port number (0-65535)`); } return port; } /** * Retrieves a duration string and converts to milliseconds. * Supports formats like "1d", "2h", "30m", "45s", "100ms" or combinations like "1h30m". * * @param {string} key - The environment variable key. * @param {string|number} [defaultValue='0'] - Optional fallback value. * @returns {number} The duration in milliseconds. * @throws {Error} If duration format is invalid. */ static getDuration(key, defaultValue = "0") { const value = Env.get(key, String(defaultValue)); if (!value) return 0; // Handle plain number input (assume milliseconds) if (/^\d+$/.test(value)) { return parseInt(value, 10); } // Parse duration string like "1d2h30m15s" const durationRegex = /(\d+d)?(\d+h)?(\d+m)?(\d+s)?(\d+ms)?/; const matches = value.match(durationRegex); if (!matches || matches[0] === "") { throw new Error(`Env ${key} has invalid duration format. Use 1d, 2h, 30m, 45s, or 100ms.`); } let ms = 0; if (matches[1]) ms += parseInt(matches[1], 10) * 86400000; // days if (matches[2]) ms += parseInt(matches[2], 10) * 3600000; // hours if (matches[3]) ms += parseInt(matches[3], 10) * 60000; // minutes if (matches[4]) ms += parseInt(matches[4], 10) * 1000; // seconds if (matches[5]) ms += parseInt(matches[5], 10); // milliseconds return ms; } /** * Gets all environment variables with sensitive values masked for safe logging. * * @param {string[]} [sensitiveKeys=['password', 'secret', 'key', 'token', 'auth']] - Keys to mask. * @returns {Record<string, string>} Environment variables with sensitive values masked. */ static getSafeEnv(sensitiveKeys = ["password", "secret", "key", "token", "auth"]) { const safeEnv = {}; for (const [key, value] of Object.entries(process.env)) { if (!value) continue; const isSensitive = sensitiveKeys.some((sensitiveKey) => key.toLowerCase().includes(sensitiveKey.toLowerCase())); safeEnv[key] = isSensitive ? "******" : value; } return safeEnv; } /** * Checks whether the specified environment variable exists. * * @param {string} key - The environment variable key. * @returns {boolean} `true` if the variable is defined, otherwise `false`. */ static has(key) { return process.env[key] !== undefined; } /** * Deletes the given environment variable (useful in tests). * * @param {string} key - The environment variable key to delete. * @returns {void} */ static delete(key) { delete process.env[key]; } } exports.Env = Env; //# sourceMappingURL=env.utils.js.map