UNPKG

@salespark/toolkit

Version:

Collection of utility functions and helpers, designed to speed up development across SalesPark projects. Includes common helpers, reusable patterns, and extended functions.

1,671 lines (1,660 loc) 48.6 kB
'use strict'; // src/utils/array.ts function uniqBy(arr, key) { const seen = /* @__PURE__ */ new Set(); const out = []; for (const item of arr) { const k = key(item); if (!seen.has(k)) { seen.add(k); out.push(item); } } return out; } function chunk(arr, size) { if (size <= 0) return [arr.slice()]; const res = []; for (let i = 0; i < arr.length; i += size) res.push(arr.slice(i, i + size)); return res; } function areArraysEqual(arr1, arr2) { try { if (!Array.isArray(arr1) || !Array.isArray(arr2)) return false; if (arr1.length !== arr2.length) return false; const normalize = (value) => { if (Array.isArray(value)) { return value.map(normalize); } if (value && typeof value === "object") { const entries = Object.entries(value).map(([k, v]) => [k, normalize(v)]).sort(([k1], [k2]) => k1 < k2 ? -1 : k1 > k2 ? 1 : 0); return Object.fromEntries(entries); } return value; }; const a = arr1.map((el) => JSON.stringify(normalize(el))).sort(); const b = arr2.map((el) => JSON.stringify(normalize(el))).sort(); for (let i = 0; i < a.length; i++) { if (a[i] !== b[i]) return false; } return true; } catch { return false; } } var areArraysDeepEqualUnordered = areArraysEqual; function uniq(arr) { return Array.from(new Set(arr)); } function flatten(arr) { return arr.flat ? arr.flat() : [].concat(...arr); } function groupBy(arr, key) { return arr.reduce((acc, item) => { const group = typeof key === "function" ? key(item) : item[key]; (acc[group] = acc[group] || []).push(item); return acc; }, {}); } function sortBy(arr, key) { return [...arr].sort((a, b) => { const aKey = typeof key === "function" ? key(a) : a[key]; const bKey = typeof key === "function" ? key(b) : b[key]; if (aKey < bKey) return -1; if (aKey > bKey) return 1; return 0; }); } function difference(arr1, arr2) { const set2 = new Set(arr2); return arr1.filter((x) => !set2.has(x)); } function intersection(arr1, arr2) { const set2 = new Set(arr2); return arr1.filter((x) => set2.has(x)); } function compact(arr) { return arr.filter(Boolean); } function pluck(arr, key) { return arr.map((item) => item[key]); } function shuffle(arr) { const a = [...arr]; for (let i = a.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [a[i], a[j]] = [a[j], a[i]]; } return a; } function isFlattenable(value) { if (Array.isArray(value)) return true; const isArgs = Object.prototype.toString.call(value) === "[object Arguments]"; if (isArgs) return true; const spreadable = Symbol.isConcatSpreadable; return spreadable ? !!value?.[spreadable] : false; } function pushAll(array, values) { let index = -1; const length = values.length; const offset = array.length; while (++index < length) { array[offset + index] = values[index]; } return array; } function flattenDepthBase(array, depth, predicate = isFlattenable, isStrict = false, result = []) { let index = -1; const length = array.length; while (++index < length) { const value = array[index]; if (depth > 0 && predicate(value)) { if (depth > 1) { flattenDepthBase( value, depth - 1, predicate, isStrict, result ); } else { pushAll(result, value); } } else if (!isStrict) { result[result.length] = value; } } return result; } function flattenOnce(array) { return flattenDepthBase(array, 1); } function flattenDepth(array, depth = 1, options) { const { predicate = isFlattenable, isStrict = false } = options ?? {}; return flattenDepthBase(array, depth, predicate, isStrict); } // src/utils/bool.ts var toBool = (value, def = false) => { try { if (value === null || value === void 0) return def; if (typeof value === "boolean") return value; if (typeof value === "number") return value === 1; if (typeof value === "string") { switch (value.toLowerCase().trim()) { case "true": case "yes": case "1": return true; case "false": case "no": case "0": return false; default: return def; } } return def; } catch { return false; } }; var parseToBool = toBool; // src/utils/object.ts function pick(obj, keys) { const out = {}; for (const k of keys) if (k in obj) out[k] = obj[k]; return out; } function omit(obj, keys) { const set = new Set(keys); const out = {}; for (const k in obj) if (!set.has(k)) out[k] = obj[k]; return out; } function objectToString(obj) { try { if (obj === null || obj === void 0) return ""; if (typeof obj !== "object") return String(obj); return JSON.stringify(obj).replace(/[{}"]/g, "").replace(/:/g, "=").replace(/,/g, "_"); } catch { try { return String(obj); } catch { return ""; } } } var isRemovable = (v) => v === null || v === void 0 || v === "null" || v === "undefined"; function cleanObject(obj) { if (Array.isArray(obj)) { const cleanedArray = obj.map((item) => cleanObject(item)).filter((item) => !isRemovable(item)); return cleanedArray; } if (obj !== null && typeof obj === "object") { const cleaned = {}; for (const [key, value] of Object.entries(obj)) { if (isRemovable(value)) continue; const nested = cleanObject(value); if (isRemovable(nested)) continue; const isObj = typeof nested === "object" && nested !== null; const isEmptyObj = isObj && !Array.isArray(nested) && Object.keys(nested).length === 0; const isEmptyArr = Array.isArray(nested) && nested.length === 0; if (isEmptyObj || isEmptyArr) continue; cleaned[key] = nested; } return cleaned; } return obj; } // src/utils/string.ts function slugify(input) { return input.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, ""); } function fill(template, values) { return template.replace(/\{(\w+)\}/g, (_, k) => String(values[k] ?? "")); } function deburr(str) { try { return str.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); } catch { return str; } } var removeDiacritics = deburr; function sanitize(input, maxLength) { if (typeof input !== "string") return ""; let cleaned = input.replace(/[\u0000-\u001F\u007F-\u009F]/g, ""); cleaned = cleaned.replace(/<(script|style|iframe)[^>]*>.*?<\/\1>/gis, ""); cleaned = cleaned.replace(/on\w+\s*=\s*(['"]).*?\1/gi, ""); cleaned = cleaned.replace(/expression\s*\([^)]*\)/gi, ""); cleaned = cleaned.replace( /&#x3C;script&#x3E;[\s\S]*?&#x3C;\/script&#x3E;/gi, "" ); cleaned = cleaned.replace(/&#x3C;.*?&#x3E;/gi, ""); cleaned = cleaned.replace(/<[^>]+>/g, ""); cleaned = cleaned.replace(/\b(javascript:|data:|vbscript:|file:|ftp:)/gi, ""); cleaned = cleaned.replace(/[^\p{L}\p{N}\s\-.,!?@#%&()]/gu, ""); if (typeof maxLength === "number" && maxLength > 0) { cleaned = cleaned.substring(0, maxLength); } cleaned = cleaned.trim(); return cleaned; } var basicSanitize = sanitize; // src/utils/number.ts var clamp = (n, min, max) => Math.min(Math.max(n, min), max); function round(n, decimals = 0) { const f = Math.pow(10, decimals); return Math.round((n + Number.EPSILON) * f) / f; } function toInteger(value, defaultValue = 0) { try { const safeValue = parseInt(String(value), 10); return isNaN(safeValue) ? defaultValue : safeValue; } catch { return defaultValue; } } var safeParseInt = toInteger; function toNumber(value, decimals = 6) { try { if (value === void 0 || value === null || value === "") return 0; let str = String(value); if (str.includes(",") && str.includes(".")) { str = str.replace(/,/g, ""); } else if (str.includes(",")) { str = str.replace(",", "."); } const num = parseFloat(str); if (isNaN(num)) return 0; return parseFloat(num.toFixed(decimals)); } catch { return 0; } } var parseToNumber = toNumber; function secureRandomIndex(max) { if (max <= 0) return 0; const g = globalThis; const crypto = g?.crypto; if (crypto && typeof crypto.getRandomValues === "function") { const range = 4294967296; const threshold = range - range % max; const buf = new Uint32Array(1); do { crypto.getRandomValues(buf); } while (buf[0] >= threshold); return buf[0] % max; } return Math.floor(Math.random() * max); } function randomDigits(length = 6, options) { const charset = options?.charset ?? "0123456789"; if (length <= 0 || charset.length === 0) return ""; let out = ""; for (let i = 0; i < length; i++) { if (i === 0 && options?.noLeadingZero && charset.includes("0")) { const pool = charset.replace(/0/g, ""); if (pool.length === 0) return ""; out += pool[secureRandomIndex(pool.length)]; } else { out += charset[secureRandomIndex(charset.length)]; } } return out; } var otp = randomDigits; // src/utils/func.ts function debounce(fn, wait = 250) { let t; return function(...args) { clearTimeout(t); t = setTimeout(() => fn.apply(this, args), wait); }; } function throttle(fn, wait = 250) { let last = 0; let timer; return function(...args) { const now = Date.now(); const remaining = wait - (now - last); if (remaining <= 0) { last = now; fn.apply(this, args); } else if (!timer) { timer = setTimeout(() => { last = Date.now(); timer = null; fn.apply(this, args); }, remaining); } }; } var isNil = (value) => value === null || value === void 0; var isNilText = (value) => { if (value === null || value === void 0) return true; if (typeof value === "string") { const v = value.trim().toLowerCase(); return v === "null" || v === "undefined"; } return false; }; var isNilOrEmpty = (value) => { try { return isNil(value) || value === ""; } catch { return true; } }; var hasNilOrEmpty = (array) => { try { if (!Array.isArray(array)) return true; for (const item of array) { if (isNilOrEmpty(item)) return true; } return false; } catch { return true; } }; var isNilEmptyOrZeroLen = (value) => { try { if (isNil(value) || value === "") return true; const maybeLen = value?.length; return typeof maybeLen === "number" && maybeLen === 0; } catch { return true; } }; var isNilOrZeroLen = (value) => { try { if (isNil(value)) return true; const maybeLen = value?.length; return typeof maybeLen === "number" && maybeLen === 0; } catch { return true; } }; var isNilOrNaN = (value) => { try { return isNil(value) || isNaN(value); } catch { return true; } }; var isNullOrUndefined = isNil; var isNullOrUndefinedTextInc = isNilText; var isNullUndefinedOrEmpty = isNilOrEmpty; var isNullOrUndefinedInArray = hasNilOrEmpty; var isNullOrUndefinedEmptyOrZero = isNilEmptyOrZeroLen; var isNullUndefinedOrZero = isNilOrZeroLen; var isNullOrUndefinedOrNaN = isNilOrNaN; var formatBytes = (bytes, si = false, dp = 1) => { if (!Number.isFinite(bytes)) return "NaN"; const thresh = si ? 1e3 : 1024; const abs = Math.abs(bytes); if (abs < thresh) return `${bytes} B`; const units = si ? ["kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"] : ["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"]; let u = -1; const r = 10 ** dp; let value = bytes; do { value /= thresh; ++u; } while (Math.round(Math.abs(value) * r) / r >= thresh && u < units.length - 1); return `${value.toFixed(dp)} ${units[u]}`; }; var humanFileSize = formatBytes; var levenshtein = (a, b) => { if (a === b) return 0; const al = a.length; const bl = b.length; if (al === 0) return bl; if (bl === 0) return al; if (al > bl) { const tmp = a; a = b; b = tmp; } const n = a.length; const m = b.length; let prev = new Array(n + 1); let curr = new Array(n + 1); for (let i = 0; i <= n; i++) prev[i] = i; for (let j = 1; j <= m; j++) { curr[0] = j; const bj = b.charCodeAt(j - 1); for (let i = 1; i <= n; i++) { const cost = a.charCodeAt(i - 1) === bj ? 0 : 1; const del = prev[i] + 1; const ins = curr[i - 1] + 1; const sub = prev[i - 1] + cost; curr[i] = del < ins ? del < sub ? del : sub : ins < sub ? ins : sub; } const tmp = prev; prev = curr; curr = tmp; } return prev[n]; }; var stringSimilarity = (s1, s2) => { const a = s1 ?? ""; const b = s2 ?? ""; let longer = a; let shorter = b; if (a.length < b.length) { longer = b; shorter = a; } const L = longer.length; if (L === 0) return 1; const dist = levenshtein(longer, shorter); return (L - dist) / L; }; var getStringSimilarity = stringSimilarity; var addThousandsSpace = (value) => { try { const str = value.toString(); const [intPart, decimalPart] = str.split("."); const formattedInt = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, " "); return decimalPart ? `${formattedInt}.${decimalPart}` : formattedInt; } catch { return value; } }; var delay = (ms) => new Promise((resolve) => setTimeout(resolve, Math.max(0, ms))); var isNilTextOrEmpty = (value) => { try { if (value === null || value === void 0 || value === "") return true; if (typeof value === "string") { const v = value.trim().toLowerCase(); return v === "null" || v === "undefined"; } return false; } catch { return true; } }; var formatCurrency = (value, withoutCurrencySymbol = false, currency = "EUR", locale = "pt-PT") => { try { const numValue = value === void 0 || value === null || value === "" ? 0 : Number(value); if (isNaN(numValue) || !isFinite(numValue)) { return withoutCurrencySymbol ? "0,00" : "0,00 \u20AC"; } const intlOptions = { style: withoutCurrencySymbol ? "decimal" : "currency", currency, minimumFractionDigits: 2, maximumFractionDigits: 2 }; return new Intl.NumberFormat(locale, intlOptions).format(numValue); } catch (error) { const numValue = Number(value) || 0; const formatted = numValue.toFixed(2).replace(".", ","); return withoutCurrencySymbol ? formatted : `${formatted} \u20AC`; } }; var parseName = (name) => { try { if (name === void 0 || name === null || name === "") { return { firstName: "", lastName: "" }; } const cleanName = name.toString().trim().replace(/\s+/g, " "); if (cleanName === "") { return { firstName: "", lastName: "" }; } if (!cleanName.includes(" ")) { return { firstName: cleanName, lastName: "" }; } const nameParts = cleanName.split(" "); const firstName = nameParts[0]; const lastName = nameParts[nameParts.length - 1]; return { firstName, lastName }; } catch (error) { const fallbackName = name ? String(name).trim() : ""; return { firstName: fallbackName, lastName: "" }; } }; var symbolToCurrency = (symbol) => { try { if (!symbol || typeof symbol !== "string") { return "EUR"; } const normalizedSymbol = symbol.trim(); switch (normalizedSymbol) { case "\u20AC": return "EUR"; case "\xA3": return "GBP"; case "$": return "USD"; case "\xA5": case "\uFFE5": return "JPY"; case "\u20B9": return "INR"; case "\u20BD": return "RUB"; case "\xA2": return "USD"; // US cents case "\u20A9": return "KRW"; // South Korean Won case "\u20AA": return "ILS"; // Israeli Shekel case "\u20A6": return "NGN"; // Nigerian Naira case "\u20A8": return "PKR"; // Pakistani Rupee case "\u20B1": return "PHP"; // Philippine Peso case "\u20AB": return "VND"; // Vietnamese Dong case "\u20A1": return "CRC"; // Costa Rican Colon case "\u20B2": return "PYG"; // Paraguayan Guarani case "\u20B4": return "UAH"; // Ukrainian Hryvnia case "\u20B5": return "GHS"; // Ghanaian Cedi case "\u20B6": return "EUR"; // Livre tournois (historical, fallback to EUR) case "\u20B8": return "KZT"; // Kazakhstani Tenge case "\u20BA": return "TRY"; // Turkish Lira case "\u20BB": return "EUR"; // Nordic mark (historical, fallback to EUR) case "\u20BC": return "AZN"; // Azerbaijani Manat case "\u20BE": return "GEL"; // Georgian Lari case "\u20BF": return "BTC"; // Bitcoin case "\uFDFC": return "SAR"; // Saudi Riyal case "\uFF04": return "USD"; // Full-width dollar sign case "\uFFE0": return "USD"; // Full-width cent sign case "\uFFE1": return "GBP"; // Full-width pound sign case "\uFFE2": return "GBP"; // Full-width not sign (sometimes used for pound) case "\uFFE3": return "JPY"; // Full-width macron (sometimes used for yen) case "\uFFE4": return "EUR"; // Full-width lira sign case "\uFFE6": return "KRW"; // Full-width won sign // Additional common symbols case "R": return "ZAR"; // South African Rand (when used as symbol) case "R$": return "BRL"; // Brazilian Real case "C$": return "CAD"; // Canadian Dollar case "A$": return "AUD"; // Australian Dollar case "S$": return "SGD"; // Singapore Dollar case "HK$": return "HKD"; // Hong Kong Dollar case "NZ$": return "NZD"; // New Zealand Dollar case "kr": case "Kr": return "SEK"; // Swedish Krona (fallback, could be NOK or DKK) case "z\u0142": return "PLN"; // Polish Zloty case "K\u010D": return "CZK"; // Czech Koruna case "Ft": return "HUF"; // Hungarian Forint case "lei": return "RON"; // Romanian Leu case "\u043B\u0432": return "BGN"; // Bulgarian Lev case "kn": return "HRK"; // Croatian Kuna case "din": return "RSD"; // Serbian Dinar case "\u0434\u0435\u043D": return "MKD"; // Macedonian Denar default: return "EUR"; } } catch (error) { return "EUR"; } }; var currencyToSymbol = (currency) => { try { if (!currency || typeof currency !== "string") { return "\u20AC"; } const normalizedCurrency = currency.trim().toUpperCase(); switch (normalizedCurrency) { case "EUR": return "\u20AC"; case "GBP": return "\xA3"; case "USD": return "$"; case "JPY": return "\xA5"; case "INR": return "\u20B9"; case "RUB": return "\u20BD"; case "CNY": return "\xA5"; case "KRW": return "\u20A9"; // South Korean Won case "ILS": return "\u20AA"; // Israeli Shekel case "NGN": return "\u20A6"; // Nigerian Naira case "PKR": return "\u20A8"; // Pakistani Rupee case "PHP": return "\u20B1"; // Philippine Peso case "VND": return "\u20AB"; // Vietnamese Dong case "CRC": return "\u20A1"; // Costa Rican Colon case "PYG": return "\u20B2"; // Paraguayan Guarani case "UAH": return "\u20B4"; // Ukrainian Hryvnia case "GHS": return "\u20B5"; // Ghanaian Cedi case "KZT": return "\u20B8"; // Kazakhstani Tenge case "TRY": return "\u20BA"; // Turkish Lira case "AZN": return "\u20BC"; // Azerbaijani Manat case "GEL": return "\u20BE"; // Georgian Lari case "BTC": return "\u20BF"; // Bitcoin case "SAR": return "\uFDFC"; // Saudi Riyal case "ZAR": return "R"; // South African Rand case "BRL": return "R$"; // Brazilian Real case "CAD": return "C$"; // Canadian Dollar case "AUD": return "A$"; // Australian Dollar case "SGD": return "S$"; // Singapore Dollar case "HKD": return "HK$"; // Hong Kong Dollar case "NZD": return "NZ$"; // New Zealand Dollar case "SEK": return "kr"; // Swedish Krona case "NOK": return "kr"; // Norwegian Krone case "DKK": return "kr"; // Danish Krone case "PLN": return "z\u0142"; // Polish Zloty case "CZK": return "K\u010D"; // Czech Koruna case "HUF": return "Ft"; // Hungarian Forint case "RON": return "lei"; // Romanian Leu case "BGN": return "\u043B\u0432"; // Bulgarian Lev case "HRK": return "kn"; // Croatian Kuna case "RSD": return "din"; // Serbian Dinar case "MKD": return "\u0434\u0435\u043D"; // Macedonian Denar case "CHF": return "CHF"; // Swiss Franc (commonly written as CHF) case "THB": return "\u0E3F"; // Thai Baht case "MYR": return "RM"; // Malaysian Ringgit case "IDR": return "Rp"; // Indonesian Rupiah case "CLP": return "$"; // Chilean Peso (uses $ symbol) case "COP": return "$"; // Colombian Peso (uses $ symbol) case "MXN": return "$"; // Mexican Peso (uses $ symbol) case "ARS": return "$"; // Argentine Peso (uses $ symbol) case "UYU": return "$"; // Uruguayan Peso (uses $ symbol) case "PEN": return "S/"; // Peruvian Sol case "BOB": return "Bs"; // Bolivian Boliviano case "EGP": return "\xA3"; // Egyptian Pound (uses £ symbol) case "LBP": return "\xA3"; // Lebanese Pound (uses £ symbol) case "SYP": return "\xA3"; // Syrian Pound (uses £ symbol) default: return "\u20AC"; } } catch (error) { return "\u20AC"; } }; var isNullUndefinedOrEmptyEnforced = isNilTextOrEmpty; var addSpaceBetweenNumbers = addThousandsSpace; // src/utils/validations.ts function isPTTaxId(value) { try { if (value === null || value === void 0) return false; let nif = String(value).trim(); nif = nif.replace(/[^0-9]/g, ""); if (nif.length !== 9) return false; if (!/^\d{9}$/.test(nif)) return false; if (/^(\d)\1{8}$/.test(nif)) return false; const first = nif[0]; const defaultAllowed = /* @__PURE__ */ new Set(["1", "2", "3", "5", "6", "8", "9"]); if (!defaultAllowed.has(first)) return false; let sum = 0; for (let i = 0; i < 8; i++) { const digit = parseInt(nif[i], 10); const weight = 9 - i; sum += digit * weight; } const mod11 = sum % 11; const checkDigit = mod11 < 2 ? 0 : 11 - mod11; return checkDigit === parseInt(nif[8], 10); } catch { return false; } } var isValidPTTaxId = isPTTaxId; // src/utils/iban.ts function isValidIBAN(value) { try { if (!value || typeof value !== "string") return false; const iban = value.replace(/[\s-]/g, "").toUpperCase(); if (iban.length < 15 || iban.length > 34) return false; if (!/^[A-Z]{2}[0-9]{2}[A-Z0-9]+$/.test(iban)) return false; const countryCode = iban.slice(0, 2); const spec = countrySpecs[countryCode]; if (!spec?.bban_regexp || !spec.chars) return false; if (spec.chars !== iban.length) return false; if (!/^[0-9]{2}$/.test(iban.slice(2, 4))) return false; const bban = iban.slice(4); if (!new RegExp(spec.bban_regexp).test(bban)) return false; if (spec.bban_validation_func && !spec.bban_validation_func(bban)) return false; return isValidIBANChecksum(iban); } catch { return false; } } function isValidIBANChecksum(iban) { const rearranged = iban.slice(4) + iban.slice(0, 4); const numericString = rearranged.split("").map((char) => { const code = char.charCodeAt(0); return code >= 65 ? (code - 55).toString() : char; }).join(""); return mod97(numericString) === 1; } function mod97(numStr) { let remainder = numStr; while (remainder.length > 2) { const chunk2 = remainder.slice(0, 9); const chunkNum = parseInt(chunk2, 10); if (isNaN(chunkNum)) return NaN; remainder = chunkNum % 97 + remainder.slice(chunk2.length); } return parseInt(remainder, 10) % 97; } var countrySpecs = { AD: { chars: 24, bban_regexp: "^[0-9]{8}[A-Z0-9]{12}$", IBANRegistry: true, SEPA: true }, AE: { chars: 23, bban_regexp: "^[0-9]{3}[0-9]{16}$", IBANRegistry: true }, AL: { chars: 28, bban_regexp: "^[0-9]{8}[A-Z0-9]{16}$", IBANRegistry: true }, AT: { chars: 20, bban_regexp: "^[0-9]{16}$", IBANRegistry: true, SEPA: true }, AZ: { chars: 28, bban_regexp: "^[A-Z]{4}[A-Z0-9]{20}$", IBANRegistry: true }, BA: { chars: 20, bban_regexp: "^[0-9]{16}$", bban_validation_func: checkMod97BBAN, IBANRegistry: true }, BE: { chars: 16, bban_regexp: "^[0-9]{12}$", bban_validation_func: checkBelgianBBAN, IBANRegistry: true, SEPA: true }, BG: { chars: 22, bban_regexp: "^[A-Z]{4}[0-9]{6}[A-Z0-9]{8}$", IBANRegistry: true, SEPA: true }, BH: { chars: 22, bban_regexp: "^[A-Z]{4}[A-Z0-9]{14}$", IBANRegistry: true }, BR: { chars: 29, bban_regexp: "^[0-9]{23}[A-Z]{1}[A-Z0-9]{1}$", IBANRegistry: true }, BY: { chars: 28, bban_regexp: "^[A-Z]{4}[0-9]{4}[A-Z0-9]{16}$", IBANRegistry: true }, CH: { chars: 21, bban_regexp: "^[0-9]{5}[A-Z0-9]{12}$", IBANRegistry: true, SEPA: true }, CR: { chars: 22, bban_regexp: "^[0-9]{18}$", IBANRegistry: true }, CY: { chars: 28, bban_regexp: "^[0-9]{8}[A-Z0-9]{16}$", IBANRegistry: true, SEPA: true }, CZ: { chars: 24, bban_regexp: "^[0-9]{20}$", bban_validation_func: checkCzechSlovakBBAN, IBANRegistry: true, SEPA: true }, DE: { chars: 22, bban_regexp: "^[0-9]{18}$", IBANRegistry: true, SEPA: true }, DK: { chars: 18, bban_regexp: "^[0-9]{14}$", IBANRegistry: true, SEPA: true }, DO: { chars: 28, bban_regexp: "^[A-Z]{4}[0-9]{20}$", IBANRegistry: true }, EE: { chars: 20, bban_regexp: "^[0-9]{16}$", bban_validation_func: checkEstonianBBAN, IBANRegistry: true, SEPA: true }, EG: { chars: 29, bban_regexp: "^[0-9]{25}$", IBANRegistry: true }, ES: { chars: 24, bban_regexp: "^[0-9]{20}$", bban_validation_func: checkSpanishBBAN, IBANRegistry: true, SEPA: true }, FI: { chars: 18, bban_regexp: "^[0-9]{14}$", IBANRegistry: true, SEPA: true }, FO: { chars: 18, bban_regexp: "^[0-9]{14}$", IBANRegistry: true }, FR: { chars: 27, bban_regexp: "^[0-9]{10}[A-Z0-9]{11}[0-9]{2}$", bban_validation_func: checkFrenchBBAN, IBANRegistry: true, SEPA: true }, GB: { chars: 22, bban_regexp: "^[A-Z]{4}[0-9]{14}$", IBANRegistry: true, SEPA: true }, GE: { chars: 22, bban_regexp: "^[A-Z0-9]{2}[0-9]{16}$", IBANRegistry: true }, GI: { chars: 23, bban_regexp: "^[A-Z]{4}[A-Z0-9]{15}$", IBANRegistry: true, SEPA: true }, GL: { chars: 18, bban_regexp: "^[0-9]{14}$", IBANRegistry: true }, GR: { chars: 27, bban_regexp: "^[0-9]{7}[A-Z0-9]{16}$", IBANRegistry: true, SEPA: true }, GT: { chars: 28, bban_regexp: "^[A-Z0-9]{24}$", IBANRegistry: true }, HR: { chars: 21, bban_regexp: "^[0-9]{17}$", bban_validation_func: checkCroatianBBAN, IBANRegistry: true, SEPA: true }, HU: { chars: 28, bban_regexp: "^[0-9]{24}$", bban_validation_func: checkHungarianBBAN, IBANRegistry: true, SEPA: true }, IE: { chars: 22, bban_regexp: "^[A-Z0-9]{4}[0-9]{14}$", IBANRegistry: true, SEPA: true }, IL: { chars: 23, bban_regexp: "^[0-9]{19}$", IBANRegistry: true }, IS: { chars: 26, bban_regexp: "^[0-9]{22}$", IBANRegistry: true, SEPA: true }, IT: { chars: 27, bban_regexp: "^[A-Z]{1}[0-9]{10}[A-Z0-9]{12}$", IBANRegistry: true, SEPA: true }, JO: { chars: 30, bban_regexp: "^[A-Z]{4}[0-9]{4}[A-Z0-9]{18}$", IBANRegistry: true }, KW: { chars: 30, bban_regexp: "^[A-Z]{4}[A-Z0-9]{22}$", IBANRegistry: true }, KZ: { chars: 20, bban_regexp: "^[0-9]{3}[A-Z0-9]{13}$", IBANRegistry: true }, LB: { chars: 28, bban_regexp: "^[0-9]{4}[A-Z0-9]{20}$", IBANRegistry: true }, LC: { chars: 32, bban_regexp: "^[A-Z]{4}[A-Z0-9]{24}$", IBANRegistry: true }, LI: { chars: 21, bban_regexp: "^[0-9]{5}[A-Z0-9]{12}$", IBANRegistry: true, SEPA: true }, LT: { chars: 20, bban_regexp: "^[0-9]{16}$", IBANRegistry: true, SEPA: true }, LU: { chars: 20, bban_regexp: "^[0-9]{3}[A-Z0-9]{13}$", IBANRegistry: true, SEPA: true }, LV: { chars: 21, bban_regexp: "^[A-Z]{4}[A-Z0-9]{13}$", IBANRegistry: true, SEPA: true }, MC: { chars: 27, bban_regexp: "^[0-9]{10}[A-Z0-9]{11}[0-9]{2}$", bban_validation_func: checkFrenchBBAN, IBANRegistry: true, SEPA: true }, MD: { chars: 24, bban_regexp: "^[A-Z0-9]{2}[A-Z0-9]{18}$", IBANRegistry: true }, ME: { chars: 22, bban_regexp: "^[0-9]{18}$", bban_validation_func: checkMod97BBAN, IBANRegistry: true }, MK: { chars: 19, bban_regexp: "^[0-9]{3}[A-Z0-9]{10}[0-9]{2}$", bban_validation_func: checkMod97BBAN, IBANRegistry: true }, MR: { chars: 27, bban_regexp: "^[0-9]{23}$", IBANRegistry: true }, MT: { chars: 31, bban_regexp: "^[A-Z]{4}[0-9]{5}[A-Z0-9]{18}$", IBANRegistry: true, SEPA: true }, MU: { chars: 30, bban_regexp: "^[A-Z]{4}[0-9]{19}[A-Z]{3}$", IBANRegistry: true }, NL: { chars: 18, bban_regexp: "^[A-Z]{4}[0-9]{10}$", IBANRegistry: true, SEPA: true }, NO: { chars: 15, bban_regexp: "^[0-9]{11}$", bban_validation_func: checkNorwegianBBAN, IBANRegistry: true, SEPA: true }, PK: { chars: 24, bban_regexp: "^[A-Z0-9]{4}[0-9]{16}$", IBANRegistry: true }, PL: { chars: 28, bban_regexp: "^[0-9]{24}$", bban_validation_func: checkPolishBBAN, IBANRegistry: true, SEPA: true }, PS: { chars: 29, bban_regexp: "^[A-Z0-9]{4}[0-9]{21}$", IBANRegistry: true }, PT: { chars: 25, bban_regexp: "^[0-9]{21}$", bban_validation_func: checkMod97BBAN, IBANRegistry: true, SEPA: true }, QA: { chars: 29, bban_regexp: "^[A-Z]{4}[A-Z0-9]{21}$", IBANRegistry: true }, RO: { chars: 24, bban_regexp: "^[A-Z]{4}[A-Z0-9]{16}$", IBANRegistry: true, SEPA: true }, RS: { chars: 22, bban_regexp: "^[0-9]{18}$", bban_validation_func: checkMod97BBAN, IBANRegistry: true }, SA: { chars: 24, bban_regexp: "^[0-9]{2}[A-Z0-9]{18}$", IBANRegistry: true }, SE: { chars: 24, bban_regexp: "^[0-9]{20}$", IBANRegistry: true, SEPA: true }, SI: { chars: 19, bban_regexp: "^[0-9]{15}$", bban_validation_func: checkMod97BBAN, IBANRegistry: true, SEPA: true }, SK: { chars: 24, bban_regexp: "^[0-9]{20}$", bban_validation_func: checkCzechSlovakBBAN, IBANRegistry: true, SEPA: true }, SM: { chars: 27, bban_regexp: "^[A-Z]{1}[0-9]{10}[A-Z0-9]{12}$", IBANRegistry: true, SEPA: true }, TN: { chars: 24, bban_regexp: "^[0-9]{20}$", IBANRegistry: true }, TR: { chars: 26, bban_regexp: "^[0-9]{5}[A-Z0-9]{17}$", IBANRegistry: true }, UA: { chars: 29, bban_regexp: "^[0-9]{6}[A-Z0-9]{19}$", IBANRegistry: true }, VG: { chars: 24, bban_regexp: "^[A-Z0-9]{4}[0-9]{16}$", IBANRegistry: true }, XK: { chars: 20, bban_regexp: "^[0-9]{16}$", IBANRegistry: true } }; function checkMod97BBAN(bban) { const stripped = bban.replace(/[\s.]+/g, ""); return mod97(stripped) === 1; } function checkBelgianBBAN(bban) { const stripped = bban.replace(/[\s.]+/g, ""); const checkingPart = parseInt(stripped.substring(0, stripped.length - 2), 10); const checksum = parseInt(stripped.substring(stripped.length - 2), 10); const remainder = checkingPart % 97 === 0 ? 97 : checkingPart % 97; return remainder === checksum; } function checkCzechSlovakBBAN(bban) { const weightsPrefix = [10, 5, 8, 4, 2, 1]; const weightsSuffix = [6, 3, 7, 9, 10, 5, 8, 4, 2, 1]; const controlPrefix = parseInt(bban.charAt(9), 10); const controlSuffix = parseInt(bban.charAt(19), 10); const prefix = bban.substring(4, 9); const suffix = bban.substring(10, 19); let sum = 0; for (let i = 0; i < prefix.length; i++) { sum += parseInt(prefix.charAt(i), 10) * weightsPrefix[i]; } let remainder = sum % 11; if (controlPrefix !== (remainder === 0 ? 0 : remainder === 1 ? 1 : 11 - remainder)) { return false; } sum = 0; for (let i = 0; i < suffix.length; i++) { sum += parseInt(suffix.charAt(i), 10) * weightsSuffix[i]; } remainder = sum % 11; return controlSuffix === (remainder === 0 ? 0 : remainder === 1 ? 1 : 11 - remainder); } function checkEstonianBBAN(bban) { const weights = [7, 1, 3, 7, 1, 3, 7, 1, 3, 7, 1, 3, 7]; const controlDigit = parseInt(bban.charAt(15), 10); const toCheck = bban.substring(2, 15); let sum = 0; for (let i = 0; i < toCheck.length; i++) { sum += parseInt(toCheck.charAt(i), 10) * weights[i]; } const remainder = sum % 10; return controlDigit === (remainder === 0 ? 0 : 10 - remainder); } function checkSpanishBBAN(bban) { const weightsBankBranch = [4, 8, 5, 10, 9, 7, 3, 6]; const weightsAccount = [1, 2, 4, 8, 5, 10, 9, 7, 3, 6]; const controlBankBranch = parseInt(bban.charAt(8), 10); const controlAccount = parseInt(bban.charAt(9), 10); const bankBranch = bban.substring(0, 8); const account = bban.substring(10, 20); let sum = 0; for (let i = 0; i < 8; i++) { sum += parseInt(bankBranch.charAt(i), 10) * weightsBankBranch[i]; } let remainder = sum % 11; if (controlBankBranch !== (remainder === 0 ? 0 : remainder === 1 ? 1 : 11 - remainder)) { return false; } sum = 0; for (let i = 0; i < 10; i++) { sum += parseInt(account.charAt(i), 10) * weightsAccount[i]; } remainder = sum % 11; return controlAccount === (remainder === 0 ? 0 : remainder === 1 ? 1 : 11 - remainder); } function checkFrenchBBAN(bban) { const stripped = bban.replace(/[\s.]+/g, ""); const normalized = Array.from(stripped); for (let i = 0; i < stripped.length; i++) { const c = normalized[i].charCodeAt(0); if (c >= 65) { switch (c) { case 65: case 74: normalized[i] = "1"; break; case 66: case 75: case 83: normalized[i] = "2"; break; case 67: case 76: case 84: normalized[i] = "3"; break; case 68: case 77: case 85: normalized[i] = "4"; break; case 69: case 78: case 86: normalized[i] = "5"; break; case 70: case 79: case 87: normalized[i] = "6"; break; case 71: case 80: case 88: normalized[i] = "7"; break; case 72: case 81: case 89: normalized[i] = "8"; break; case 73: case 82: case 90: normalized[i] = "9"; break; } } } return mod97(normalized.join("")) === 0; } function checkCroatianBBAN(bban) { const controlBankBranch = parseInt(bban.charAt(6), 10); const controlAccount = parseInt(bban.charAt(16), 10); const bankBranch = bban.substring(0, 6); const account = bban.substring(7, 16); return checkMod11(bankBranch, controlBankBranch) && checkMod11(account, controlAccount); } function checkMod11(toCheck, control) { let nr = 10; for (let i = 0; i < toCheck.length; i++) { nr += parseInt(toCheck.charAt(i), 10); if (nr % 10 !== 0) { nr = nr % 10; } nr = nr * 2; nr = nr % 11; } return control === (11 - nr === 10 ? 0 : 11 - nr); } function checkHungarianBBAN(bban) { const weights = [9, 7, 3, 1, 9, 7, 3, 1, 9, 7, 3, 1, 9, 7, 3]; const controlDigitBankBranch = parseInt(bban.charAt(7), 10); const toCheckBankBranch = bban.substring(0, 7); let sum = 0; for (let i = 0; i < toCheckBankBranch.length; i++) { sum += parseInt(toCheckBankBranch.charAt(i), 10) * weights[i]; } const remainder = sum % 10; if (controlDigitBankBranch !== (remainder === 0 ? 0 : 10 - remainder)) { return false; } if (bban.endsWith("00000000")) { const toCheckAccount = bban.substring(8, 15); const controlDigitAccount = parseInt(bban.charAt(15), 10); sum = 0; for (let i = 0; i < toCheckAccount.length; i++) { sum += parseInt(toCheckAccount.charAt(i), 10) * weights[i]; } const accountRemainder = sum % 10; return controlDigitAccount === (accountRemainder === 0 ? 0 : 10 - accountRemainder); } else { const toCheckAccount = bban.substring(8, 23); const controlDigitAccount = parseInt(bban.charAt(23), 10); sum = 0; for (let i = 0; i < toCheckAccount.length; i++) { sum += parseInt(toCheckAccount.charAt(i), 10) * weights[i]; } const accountRemainder = sum % 10; return controlDigitAccount === (accountRemainder === 0 ? 0 : 10 - accountRemainder); } } function checkNorwegianBBAN(bban) { const weights = [5, 4, 3, 2, 7, 6, 5, 4, 3, 2]; const stripped = bban.replace(/[\s.]+/g, ""); const controlDigit = parseInt(stripped.charAt(10), 10); const toCheck = stripped.substring(0, 10); let sum = 0; for (let i = 0; i < 10; i++) { sum += parseInt(toCheck.charAt(i), 10) * weights[i]; } const remainder = sum % 11; return controlDigit === (remainder === 0 ? 0 : 11 - remainder); } function checkPolishBBAN(bban) { const weights = [3, 9, 7, 1, 3, 9, 7]; const controlDigit = parseInt(bban.charAt(7), 10); const toCheck = bban.substring(0, 7); let sum = 0; for (let i = 0; i < 7; i++) { sum += parseInt(toCheck.charAt(i), 10) * weights[i]; } const remainder = sum % 10; return controlDigit === (remainder === 0 ? 0 : 10 - remainder); } // src/utils/security.ts var checkMarkdownSecurity = (markdownText) => { if (!markdownText || typeof markdownText !== "string") { return { isValid: true, text: "", risks: [], sanitized: false }; } const securityCheck = { isValid: true, text: markdownText, risks: [], sanitized: false }; const securityPatterns = { // Critical risks scriptTags: { pattern: /<\s*script\b[^>]*>[\s\S]*?<\s*\/\s*script\s*>/gi, severity: "critical", description: "Script tags detected - high XSS risk" }, eventHandlers: { pattern: /on\w+\s*=\s*["'][^"']*["']/gi, severity: "critical", description: "JavaScript event handlers detected" }, javascriptUrls: { pattern: /javascript\s*:[^"'\s>]*/gi, severity: "critical", description: "JavaScript URLs detected" }, // High risks iframes: { pattern: /<\s*iframe\b[^>]*>[\s\S]*?<\s*\/\s*iframe\s*>/gi, severity: "high", description: "Iframe elements detected - potential embedding risk" }, objectTags: { pattern: /<\s*object\b[^>]*>[\s\S]*?<\s*\/\s*object\s*>/gi, severity: "high", description: "Object tags detected - potential code execution" }, embedTags: { pattern: /<\s*embed\b[^>]*>[\s\S]*?<\s*\/\s*embed\s*>/gi, severity: "high", description: "Embed tags detected - potential code execution" }, formTags: { pattern: /<\s*form\b[^>]*>[\s\S]*?<\s*\/\s*form\s*>/gi, severity: "high", description: "Form elements detected - potential data submission risk" }, // Medium risks dataUrls: { pattern: /data:\s*[^,\s]+,[^"'\s)>]*/gi, severity: "medium", description: "Data URLs detected - potential data exfiltration" }, baseTags: { pattern: /<\s*base\b[^>]*\/?>/gi, severity: "medium", description: "Base tags detected - potential URL hijacking" }, styleTags: { pattern: /<\s*style\b[^>]*>[\s\S]*?<\s*\/\s*style\s*>/gi, severity: "medium", description: "Style tags detected - potential CSS injection" }, linkTags: { pattern: /<\s*link\b[^>]*\/?>/gi, severity: "medium", description: "Link tags detected - potential resource hijacking" }, // Low risks metaTags: { pattern: /<\s*meta\b[^>]*\/?>/gi, severity: "low", description: "Meta tags detected - potential information disclosure" } }; const additionalChecks = [ { pattern: /(file|ftp|ws|wss|gopher|ldap|telnet):/gi, type: "suspiciousProtocol", description: "Suspicious URL protocol detected", severity: "high" }, { pattern: /(contenteditable|autofocus|formaction|srcdoc|srclang)/gi, type: "dangerousAttributes", description: "Potentially dangerous HTML attributes detected", severity: "medium" }, { pattern: /(expression|eval|setTimeout|setInterval|Function|constructor)/gi, type: "dangerousJavaScript", description: "Potentially dangerous JavaScript functions detected", severity: "high" }, { pattern: /(vbscript|livescript|mocha):/gi, type: "dangerousScriptProtocol", description: "Dangerous scripting protocols detected", severity: "critical" }, { pattern: /<!--[\s\S]*?-->/g, type: "htmlComments", description: "HTML comments detected - potential information leakage", severity: "low" } ]; const sanitizeContent = (text, pattern, replacement = "") => { return text.replace(pattern, replacement); }; let contentWasSanitized = false; Object.entries(securityPatterns).forEach(([riskType, config]) => { if (config.pattern.test(markdownText)) { securityCheck.isValid = false; securityCheck.risks.push({ type: riskType, description: config.description, severity: config.severity }); securityCheck.text = sanitizeContent(securityCheck.text, config.pattern); contentWasSanitized = true; } }); additionalChecks.forEach((check) => { if (check.pattern.test(markdownText)) { securityCheck.isValid = false; securityCheck.risks.push({ type: check.type, description: check.description, severity: check.severity }); securityCheck.text = sanitizeContent(securityCheck.text, check.pattern); contentWasSanitized = true; } }); securityCheck.sanitized = contentWasSanitized; const severityOrder = { critical: 0, high: 1, medium: 2, low: 3 }; securityCheck.risks.sort((a, b) => { const aSeverity = severityOrder[a.severity || "low"]; const bSeverity = severityOrder[b.severity || "low"]; return aSeverity - bSeverity; }); return securityCheck; }; var sanitizeMarkdown = (text) => { if (!text || typeof text !== "string") { return ""; } try { return text.replace(/<[^>]*>/g, "").replace(/javascript:/gi, "").replace(/data:[^,]*,[^"'\s>)]*/gi, "").replace(/(file|ftp|ws|wss|vbscript|livescript|mocha):/gi, "").replace(/&[#\w]+;/g, "").replace(/expression\s*\([^)]*\)/gi, "").replace(/\s+/g, " ").trim(); } catch (error) { return ""; } }; var assessSecurityRisks = (risks) => { if (!risks || risks.length === 0) { return { score: 0, level: "safe", recommendations: ["Content appears safe to use"] }; } const severityScores = { critical: 100, high: 50, medium: 20, low: 5 }; const score = risks.reduce((total, risk) => { return total + (severityScores[risk.severity || "low"] || 5); }, 0); let level; if (score >= 100) level = "critical"; else if (score >= 50) level = "high"; else if (score >= 20) level = "medium"; else if (score >= 5) level = "low"; else level = "safe"; const recommendations = []; const hasCritical = risks.some((r) => r.severity === "critical"); const hasHigh = risks.some((r) => r.severity === "high"); if (hasCritical) { recommendations.push("URGENT: Critical security risks detected - do not render this content"); recommendations.push("Use aggressive sanitization before any processing"); } if (hasHigh) { recommendations.push("High security risks detected - sanitization strongly recommended"); recommendations.push("Consider rejecting this content or applying strict filtering"); } if (level === "medium") { recommendations.push("Medium security risks detected - apply sanitization"); recommendations.push("Review content carefully before use"); } if (level === "low") { recommendations.push("Low security risks detected - basic sanitization recommended"); } recommendations.push("Always validate content from untrusted sources"); recommendations.push("Consider implementing Content Security Policy (CSP)"); return { score, level, recommendations }; }; // src/index.ts var isBrowser = typeof globalThis !== "undefined" && typeof globalThis.document !== "undefined"; var isNode = typeof process !== "undefined" && !!process.versions?.node; exports.addSpaceBetweenNumbers = addSpaceBetweenNumbers; exports.addThousandsSpace = addThousandsSpace; exports.areArraysDeepEqualUnordered = areArraysDeepEqualUnordered; exports.areArraysEqual = areArraysEqual; exports.assessSecurityRisks = assessSecurityRisks; exports.basicSanitize = basicSanitize; exports.checkMarkdownSecurity = checkMarkdownSecurity; exports.chunk = chunk; exports.clamp = clamp; exports.cleanObject = cleanObject; exports.compact = compact; exports.currencyToSymbol = currencyToSymbol; exports.debounce = debounce; exports.deburr = deburr; exports.delay = delay; exports.difference = difference; exports.fill = fill; exports.flatten = flatten; exports.flattenDepth = flattenDepth; exports.flattenDepthBase = flattenDepthBase; exports.flattenOnce = flattenOnce; exports.formatBytes = formatBytes; exports.formatCurrency = formatCurrency; exports.getStringSimilarity = getStringSimilarity; exports.groupBy = groupBy; exports.hasNilOrEmpty = hasNilOrEmpty; exports.humanFileSize = humanFileSize; exports.intersection = intersection; exports.isBrowser = isBrowser; exports.isFlattenable = isFlattenable; exports.isNil = isNil; exports.isNilEmptyOrZeroLen = isNilEmptyOrZeroLen; exports.isNilOrEmpty = isNilOrEmpty; exports.isNilOrNaN = isNilOrNaN; exports.isNilOrZeroLen = isNilOrZeroLen; exports.isNilText = isNilText; exports.isNilTextOrEmpty = isNilTextOrEmpty; exports.isNode = isNode; exports.isNullOrUndefined = isNullOrUndefined; exports.isNullOrUndefinedEmptyOrZero = isNullOrUndefinedEmptyOrZero; exports.isNullOrUndefinedInArray = isNullOrUndefinedInArray; exports.isNullOrUndefinedOrNaN = isNullOrUndefinedOrNaN; exports.isNullOrUndefinedTextInc = isNullOrUndefinedTextInc; exports.isNullUndefinedOrEmpty = isNullUndefinedOrEmpty; exports.isNullUndefinedOrEmptyEnforced = isNullUndefinedOrEmptyEnforced; exports.isNullUndefinedOrZero = isNullUndefinedOrZero; exports.isPTTaxId = isPTTaxId; exports.isValidIBAN = isValidIBAN; exports.isValidPTTaxId = isValidPTTaxId; exports.objectToString = objectToString; exports.omit = omit; exports.otp = otp; exports.parseName = parseName; exports.parseToBool = parseToBool; exports.parseToNumber = parseToNumber; exports.pick = pick; exports.pluck = pluck; exports.pushAll = pushAll; exports.randomDigits = randomDigits; exports.removeDiacritics = removeDiacritics; exports.round = round; exports.safeParseInt = safeParseInt; exports.sanitize = sanitize; exports.sanitizeMarkdown = sanitizeMarkdown; exports.shuffle = shuffle; exports.slugify = slugify; exports.sortBy = sortBy; exports.stringSimilarity = stringSimilarity; exports.symbolToCurrency = symbolToCurrency; exports.throttle = throttle; exports.toBool = toBool; exports.toInteger = toInteger; exports.toNumber = toNumber; exports.uniq = uniq; exports.uniqBy = uniqBy; //# sourceMappingURL=index.cjs.map //# sourceMappingURL=index.cjs.map