UNPKG

defuss-runtime

Version:

Isomorphic JS runtime API enhancements, relevant for Defuss packages.

728 lines (673 loc) 21.9 kB
'use strict'; const _HEX_PREFIX = "hex:"; function binaryToHex(buffer) { const bytes = new Uint8Array(buffer); let result = ""; for (let i = 0; i < bytes.byteLength; i++) { result += bytes[i].toString(16).padStart(2, "0"); } return `${_HEX_PREFIX}${result}`; } function hexToBinary(hexString) { const bytes = new Uint8Array(hexString.length / 2); for (let i = 0; i < bytes.length; i++) { const hexByte = hexString.slice(i * 2, i * 2 + 2); bytes[i] = Number.parseInt(hexByte, 16); } return bytes.buffer; } function textToBinary(text) { return new TextEncoder().encode(text).buffer; } function binaryToText(buffer) { return new TextDecoder().decode(new Uint8Array(buffer)); } const _BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; exports._base64LookupMap = null; function _getBase64LookupMap() { if (!exports._base64LookupMap) { exports._base64LookupMap = {}; for (let i = 0; i < _BASE64_CHARS.length; i++) { exports._base64LookupMap[_BASE64_CHARS[i]] = i; } } return exports._base64LookupMap; } function binaryToBase64(buffer) { const bytes = new Uint8Array(buffer); if (bytes.length === 0) return ""; let result = ""; for (let i = 0; i < bytes.length; i += 3) { const byte1 = bytes[i]; const byte2 = i + 1 < bytes.length ? bytes[i + 1] : 0; const byte3 = i + 2 < bytes.length ? bytes[i + 2] : 0; const chunk1 = byte1 >> 2; const chunk2 = (byte1 & 3) << 4 | byte2 >> 4; const chunk3 = (byte2 & 15) << 2 | byte3 >> 6; const chunk4 = byte3 & 63; result += _BASE64_CHARS[chunk1]; result += _BASE64_CHARS[chunk2]; result += i + 1 < bytes.length ? _BASE64_CHARS[chunk3] : "="; result += i + 2 < bytes.length ? _BASE64_CHARS[chunk4] : "="; } return result; } function base64ToBinary(base64) { if (base64.startsWith(_HEX_PREFIX)) { return hexToBinary(base64.slice(_HEX_PREFIX.length)); } try { const input = base64.replace(/=+$/, ""); if (input.length === 0) return new ArrayBuffer(0); const lookupMap = _getBase64LookupMap(); const outputLength = Math.floor(input.length * 3 / 4); const bytes = new Uint8Array(outputLength); let outputIndex = 0; for (let i = 0; i < input.length; i += 4) { const chunk1 = lookupMap[input[i]] || 0; const chunk2 = lookupMap[input[i + 1]] || 0; const chunk3 = i + 2 < input.length ? lookupMap[input[i + 2]] || 0 : 0; const chunk4 = i + 3 < input.length ? lookupMap[input[i + 3]] || 0 : 0; bytes[outputIndex++] = chunk1 << 2 | chunk2 >> 4; if (i + 2 < input.length) { bytes[outputIndex++] = (chunk2 & 15) << 4 | chunk3 >> 2; } if (i + 3 < input.length) { bytes[outputIndex++] = (chunk3 & 3) << 6 | chunk4; } } return bytes.buffer; } catch (e) { console.error("Failed to decode base64 data", e); return new ArrayBuffer(0); } } const ARRAY_INDEX = /(.*)\[(\d+)\]/; const IS_NUMBER = /^\d+$/; const getAllKeysFromPath = (path) => path.split(".").flatMap((key) => { const match = key.match(ARRAY_INDEX); return match ? [...getAllKeysFromPath(match[1]), Number(match[2])] : key; }); const ensureKey = (obj, key, nextKey) => { if (!(key in obj)) { obj[key] = IS_NUMBER.test(String(nextKey)) ? [] : {}; } }; const getByPath = (obj, path) => { const keys = getAllKeysFromPath(path); return keys.reduce( (result, key) => result == null ? void 0 : result[key], obj ); }; const setByPath = (obj, path, value) => { const keys = getAllKeysFromPath(path); const key = keys[0]; const newObj = Array.isArray(obj) ? [...obj] : { ...obj }; if (keys.length === 1) { if (value === void 0) { Array.isArray(newObj) ? newObj.splice(Number(key), 1) : delete newObj[key]; } else { newObj[key] = value; } return newObj; } ensureKey(newObj, key, keys[1]); newObj[key] = setByPath(newObj[key], keys.slice(1).join("."), value); return newObj; }; function unique(a) { return Array.from(new Set(a)); } function pick(o, keys) { const result = {}; for (const key of keys) { if (key in o) { result[key] = o[key]; } } return result; } function omit(o, keys) { const result = {}; const keysToOmit = new Set(keys); for (const key in o) { if (Object.prototype.hasOwnProperty.call(o, key) && !keysToOmit.has(key)) { result[key] = o[key]; } } return result; } function equalsJSON(a, b) { if (typeof a === "undefined" && typeof b === "undefined") { return true; } try { const normalizedA = JSON.parse(JSON.stringify(a)); const normalizedB = JSON.parse(JSON.stringify(b)); return JSON.stringify(normalizedA) === JSON.stringify(normalizedB); } catch { return false; } } const PATH_ACCESSOR_SYMBOL = Symbol("PathAccessor"); const createProxy = (currentPath = []) => { const pathString = currentPath.reduce((acc, segment, index) => { if (index === 0) { return segment; } if (segment.startsWith("[")) { return `${acc}${segment}`; } return `${acc}.${segment}`; }, ""); const target = Object.create(String.prototype); target.value = pathString; return new Proxy(target, { get(target2, prop) { if (prop === PATH_ACCESSOR_SYMBOL) { return true; } if (prop === "toString" || prop === "valueOf") { return () => pathString; } if (prop === Symbol.toPrimitive) { return (hint) => pathString; } if (typeof prop === "symbol") { return void 0; } if (typeof prop === "string" && /^\d+$/.test(prop)) { const newPath2 = [...currentPath, `[${prop}]`]; return createProxy(newPath2); } const newPath = [...currentPath, prop]; return createProxy(newPath); }, // This is crucial for equality comparisons getPrototypeOf(target2) { return String.prototype; }, // Make the proxy appear as a primitive string has(target2, prop) { if (prop === PATH_ACCESSOR_SYMBOL || prop === Symbol.toPrimitive || prop === "toString" || prop === "valueOf") { return true; } return prop in String.prototype; }, ownKeys(target2) { return ["value"]; }, getOwnPropertyDescriptor(target2, prop) { if (prop === PATH_ACCESSOR_SYMBOL) { return { configurable: true, enumerable: false, value: true, writable: false }; } if (prop === "toString" || prop === "valueOf") { return { configurable: true, enumerable: false, value: () => pathString, writable: false }; } if (prop === Symbol.toPrimitive) { return { configurable: true, enumerable: false, value: (hint) => pathString, writable: false }; } if (prop === "value") { return { configurable: true, enumerable: false, value: pathString, writable: false }; } return void 0; } }); }; const access = () => createProxy(); const isPathAccessor = (obj) => typeof obj !== "undefined" && obj !== null && obj[PATH_ACCESSOR_SYMBOL] === true; function debounce(fn, wait) { let timeout; return function(...args) { clearTimeout(timeout); timeout = setTimeout(() => fn.apply(this, args), wait); }; } function throttle(fn, wait) { let lastTime = 0; return function(...args) { const now = Date.now(); if (now - lastTime >= wait) { lastTime = now; fn.apply(this, args); } }; } function wait(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } function createTimeoutPromise(timeoutMs, operation, timeoutCallback) { return new Promise((resolve, reject) => { const timeoutId = setTimeout(() => { timeoutCallback?.(timeoutMs); reject(new Error(`Timeout after ${timeoutMs}ms`)); }, timeoutMs); Promise.resolve().then(async () => { try { const result = await operation(); clearTimeout(timeoutId); resolve(result); } catch (error) { clearTimeout(timeoutId); reject(error); } }); }); } async function waitForWithPolling(check, timeout, interval = 1) { return createTimeoutPromise(timeout, () => { return new Promise((resolve, reject) => { const timer = setInterval(() => { try { const result = check(); if (result != null) { clearInterval(timer); resolve(result); } } catch (err) { clearInterval(timer); reject(err); } }, interval); }); }); } async function waitForRef(ref, timeout) { return waitForWithPolling(() => ref.current, timeout); } const asString = (value) => { if (value === null || value === void 0) { return ""; } if (typeof value === "string") { return value; } if (typeof value === "number" || typeof value === "boolean") { return String(value); } if (value instanceof Date) { return value.toISOString(); } if (value instanceof RegExp) { return value.toString(); } if (Array.isArray(value)) { return value.join(", "); } if (typeof value === "object") { try { return JSON.stringify(value); } catch { return String(value); } } return String(value); }; const asNumber = (value) => { if (typeof value === "number") { return value; } if (typeof value === "string") { const parsed = Number.parseFloat(value); return Number.isNaN(parsed) ? 0 : parsed; } if (value instanceof Date) { return value.getTime(); } return 0; }; const asBoolean = (value) => { if (typeof value === "boolean") return value; if (typeof value === "string") { const lowerValue = value.toLowerCase(); return lowerValue === "true" || lowerValue === "1" || lowerValue === "yes" || lowerValue === "on"; } if (typeof value === "number") return value !== 0; return Boolean(value); }; const asArray = (value, transformerFn) => { if (Array.isArray(value)) { return value.map(transformerFn); } if (value === null || value === void 0) { return []; } const transformedValue = transformerFn(value); return Array.isArray(transformedValue) ? transformedValue : [transformedValue]; }; const asDate = (value) => { if (value === null || value === void 0) return new Date(Number.NaN); if (value instanceof Date) return value; const date = new Date(value); return Number.isNaN(date.getTime()) ? new Date(Number.NaN) : date; }; const asInteger = (value) => { const number = asNumber(value); if (typeof number === "number" && Number.isInteger(number)) { return number; } return asNumber(Number.parseInt(asString(number), 10).toFixed(0)); }; const isAfter = (value, minDate, inclusive = false, message) => { const isValid = value instanceof Date && (inclusive ? value.getTime() >= minDate.getTime() : value.getTime() > minDate.getTime()); return isValid ? true : message || false; }; const isArray = (value, message) => { const isValid = Array.isArray(value); return isValid ? true : message || false; }; const isBefore = (value, maxDate, inclusive = false, message) => { const isValid = value instanceof Date && (inclusive ? value.getTime() <= maxDate.getTime() : value.getTime() < maxDate.getTime()); return isValid ? true : message || false; }; const isBoolean = (value, message) => { const isValid = typeof value === "boolean"; return isValid ? true : message || false; }; const isDate = (value, message) => { const isValid = value instanceof Date && !Number.isNaN(value.getDate()); return isValid ? true : message || false; }; const isDefined = (value, message) => { const isValid = typeof value !== "undefined"; return isValid ? true : message || false; }; const isString = (value, message) => { const isValid = typeof value === "string"; return isValid ? true : message || false; }; const isEmail = (value, message) => { const stringResult = isString(value); const isStringValid = stringResult === true; const isValid = isStringValid && /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\\x01\-\\x08\\x0b\\x0c\\x0e\-\\x1f\\x21\\x23\-\\x5b\\x5d\-\\x7f]|\\[\\x01-\\x09\\x0b\\x0c\\x0e\-\\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\\x01\-\\x08\\x0b\\x0c\\x0e\-\\x1f\\x21\-\\x5a\\x53\-\\x7f]|\\[\\x01\-\\x09\\x0b\\x0c\\x0e\-\\x7f])+)\])/.test( value ); return isValid ? true : message || false; }; const isEmpty = (value, message) => { let isValid = false; if (value === null || value === void 0) isValid = true; else if (typeof value === "string") isValid = value === ""; else if (Array.isArray(value)) isValid = value.length === 0; else if (value instanceof Date) isValid = false; else if (typeof value === "object") isValid = Object.keys(value).length === 0; else isValid = false; return isValid ? true : message || false; }; const is = (value, valueB, message) => { const isValid = value === valueB; return isValid ? true : message || false; }; const isSafeNumber = (value, message) => { const isValid = typeof value === "number" && !Number.isNaN(value) && Number.isFinite(value); return isValid ? true : message || false; }; const isGreaterThan = (value, minValue, includeEqual = false, message) => { const safeNumberResult = isSafeNumber(value); const isSafeNum = safeNumberResult === true; const isValid = isSafeNum && (includeEqual ? value >= minValue : value > minValue); return isValid ? true : message || false; }; const isSafeNumeric = (value, message) => { let isValid = false; if (typeof value === "number") { const result = isSafeNumber(value); isValid = result === true; } else if (typeof value === "string") { if (value.trim() === "") { isValid = false; } else { const num = Number(value); const result = isSafeNumber(num); isValid = result === true; } } else { isValid = false; } return isValid ? true : message || false; }; const isObject = (value, message) => { const isValid = value !== null && typeof value === "object" && !Array.isArray(value); return isValid ? true : message || false; }; const isOneOf = (value, options, message) => { const isValid = options.includes(value); return isValid ? true : message || false; }; const isPhoneNumber = (value, message) => { const isValid = isString(value) && /\+(9[976]\d|8[987530]\d|6[987]\d|5[90]\d|42\d|3[875]\d|2[98654321]\d|9[8543210]|8[6421]|6[6543210]|5[87654321]|4[987654310]|3[9643210]|2[70]|7|1)\d{1,14}$/.test( value ); return isValid ? true : message || false; }; const isRequired = (value, message) => { const isValid = !!value; return isValid ? true : message || false; }; const isSlug = (value, message) => { const stringResult = isString(value); const isStringValid = stringResult === true; const isValid = isStringValid && /^[a-z0-9-]+$/.test(value); return isValid ? true : message || false; }; const isLessThan = (value, maxValue, includeEqual = false, message) => { const safeNumberResult = isSafeNumber(value); const isSafeNum = safeNumberResult === true; const isValid = isSafeNum && (includeEqual ? value <= maxValue : value < maxValue); return isValid ? true : message || false; }; const isUrl = (value, message) => { const stringResult = isString(value); if (stringResult !== true) return message || false; try { new URL(value); return true; } catch (_) { return message || false; } }; const isUrlPath = (value, message) => { const stringCheck = isString(value); const isValid = stringCheck === true && value.length > 0 && /^[a-z0-9\-_\/]+$/.test(value); return isValid ? true : message || false; }; const getDateValue = (date) => ({ year: date.getFullYear(), month: date.getMonth(), date: date.getDate(), minute: date.getMinutes(), hour: date.getHours(), second: date.getSeconds(), millisecond: date.getMilliseconds() }); const isLongerThan = (value, minLength, includeEqual = false, message) => { if (typeof value !== "string") return message || false; const isValid = includeEqual ? value.length >= minLength : value.length > minLength; return isValid ? true : message || false; }; const isShorterThan = (value, maxLength, includeEqual = false, message) => { if (typeof value !== "string") return message || false; const isValid = includeEqual ? value.length <= maxLength : value.length < maxLength; return isValid ? true : message || false; }; const hasPattern = (value, pattern, message) => { if (typeof value !== "string") return message || false; const isValid = pattern.test(value); return isValid ? true : message || false; }; const hasDateFormat = (value, message) => { if (typeof value === "bigint" || typeof value === "symbol") { return message || false; } if (typeof value === "number") { try { const date = new Date(value); const isValid = !Number.isNaN(date.getTime()); return isValid ? true : message || false; } catch { return message || false; } } const str = asString(value); if (!str) { return message || false; } try { const date = new Date(str); const isValid = !Number.isNaN(date.getTime()); return isValid ? true : message || false; } catch { return message || false; } }; const isInteger = (value, message) => { const isValid = isSafeNumber(value) && Number.isInteger(value); return isValid ? true : message || false; }; const isEqual = (value, valueB, message) => { const isValid = equalsJSON(value, valueB); return isValid ? true : message || false; }; const isTrue = (value, message) => { const isValid = value === true; return isValid ? true : message || false; }; const isFalse = (value, message) => { const isValid = value === false; return isValid ? true : message || false; }; const isTruthy = (value, message) => { const isValid = !!value; return isValid ? true : message || false; }; const isFalsy = (value, message) => { const isValid = !value; return isValid ? true : message || false; }; const isInstanceOf = (value, someConstructorFunction, message) => { if (typeof someConstructorFunction !== "function") { throw new TypeError("Expected a constructor function"); } const isValid = value instanceof someConstructorFunction && value.constructor === someConstructorFunction; return isValid ? true : message || false; }; const isTypeOf = (value, type, message) => { let isValid = false; if (type === "string") { isValid = typeof value === "string"; } else if (type === "number") { isValid = typeof value === "number"; } else if (type === "boolean") { isValid = typeof value === "boolean"; } else if (type === "object") { isValid = typeof value === "object" && value !== null; } else if (type === "function") { isValid = typeof value === "function"; } else if (type === "undefined") { isValid = typeof value === "undefined"; } return isValid ? true : message || false; }; const isNull = (value, message) => { const isValid = value === null; return isValid ? true : message || false; }; function preload(url, as = "image") { const urls = Array.isArray(url) ? url : [url]; urls.forEach((u) => { const p = document.createElement("link"); p.href = u; p.rel = "preload"; p.as = as; document.head.appendChild(p); }); } exports.PATH_ACCESSOR_SYMBOL = PATH_ACCESSOR_SYMBOL; exports._BASE64_CHARS = _BASE64_CHARS; exports._HEX_PREFIX = _HEX_PREFIX; exports._getBase64LookupMap = _getBase64LookupMap; exports.access = access; exports.asArray = asArray; exports.asBoolean = asBoolean; exports.asDate = asDate; exports.asInteger = asInteger; exports.asNumber = asNumber; exports.asString = asString; exports.base64ToBinary = base64ToBinary; exports.binaryToBase64 = binaryToBase64; exports.binaryToHex = binaryToHex; exports.binaryToText = binaryToText; exports.createTimeoutPromise = createTimeoutPromise; exports.debounce = debounce; exports.ensureKey = ensureKey; exports.equalsJSON = equalsJSON; exports.getAllKeysFromPath = getAllKeysFromPath; exports.getByPath = getByPath; exports.getDateValue = getDateValue; exports.hasDateFormat = hasDateFormat; exports.hasPattern = hasPattern; exports.hexToBinary = hexToBinary; exports.is = is; exports.isAfter = isAfter; exports.isArray = isArray; exports.isBefore = isBefore; exports.isBoolean = isBoolean; exports.isDate = isDate; exports.isDefined = isDefined; exports.isEmail = isEmail; exports.isEmpty = isEmpty; exports.isEqual = isEqual; exports.isFalse = isFalse; exports.isFalsy = isFalsy; exports.isGreaterThan = isGreaterThan; exports.isInstanceOf = isInstanceOf; exports.isInteger = isInteger; exports.isLessThan = isLessThan; exports.isLongerThan = isLongerThan; exports.isNull = isNull; exports.isObject = isObject; exports.isOneOf = isOneOf; exports.isPathAccessor = isPathAccessor; exports.isPhoneNumber = isPhoneNumber; exports.isRequired = isRequired; exports.isSafeNumber = isSafeNumber; exports.isSafeNumeric = isSafeNumeric; exports.isShorterThan = isShorterThan; exports.isSlug = isSlug; exports.isString = isString; exports.isTrue = isTrue; exports.isTruthy = isTruthy; exports.isTypeOf = isTypeOf; exports.isUrl = isUrl; exports.isUrlPath = isUrlPath; exports.omit = omit; exports.pick = pick; exports.preload = preload; exports.setByPath = setByPath; exports.textToBinary = textToBinary; exports.throttle = throttle; exports.unique = unique; exports.wait = wait; exports.waitForRef = waitForRef; exports.waitForWithPolling = waitForWithPolling;