defuss-runtime
Version:
Isomorphic JS runtime API enhancements, relevant for Defuss packages.
659 lines (605 loc) • 20.5 kB
JavaScript
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+/";
let _base64LookupMap = null;
function _getBase64LookupMap() {
if (!_base64LookupMap) {
_base64LookupMap = {};
for (let i = 0; i < _BASE64_CHARS.length; i++) {
_base64LookupMap[_BASE64_CHARS[i]] = i;
}
}
return _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);
});
}
export { PATH_ACCESSOR_SYMBOL, _BASE64_CHARS, _HEX_PREFIX, _base64LookupMap, _getBase64LookupMap, access, asArray, asBoolean, asDate, asInteger, asNumber, asString, base64ToBinary, binaryToBase64, binaryToHex, binaryToText, createTimeoutPromise, debounce, ensureKey, equalsJSON, getAllKeysFromPath, getByPath, getDateValue, hasDateFormat, hasPattern, hexToBinary, is, isAfter, isArray, isBefore, isBoolean, isDate, isDefined, isEmail, isEmpty, isEqual, isFalse, isFalsy, isGreaterThan, isInstanceOf, isInteger, isLessThan, isLongerThan, isNull, isObject, isOneOf, isPathAccessor, isPhoneNumber, isRequired, isSafeNumber, isSafeNumeric, isShorterThan, isSlug, isString, isTrue, isTruthy, isTypeOf, isUrl, isUrlPath, omit, pick, preload, setByPath, textToBinary, throttle, unique, wait, waitForRef, waitForWithPolling };