tiny-essentials
Version:
Collection of small, essential scripts designed to be used across various projects. These simple utilities are crafted for speed, ease of use, and versatility.
300 lines (299 loc) • 9.9 kB
JavaScript
import { Buffer } from 'buffer';
import { countObj, isJsonObject } from './objChecker.mjs';
export { countObj, isJsonObject };
const isBrowser = typeof window !== 'undefined' && typeof window.document !== 'undefined';
/**
* An object containing type validation functions and their evaluation order.
*
* Each item in `typeValidator.items` is a function that receives any value
* and returns a boolean indicating whether the value matches the corresponding type.
*
* The `order` array defines the priority in which types should be checked,
* which can be useful for functions that infer types in a consistent manner.
*
*/
const typeValidator = {
items: {},
/**
* Evaluation order of the type checkers.
* @type {string[]}
* */
order: [],
};
/** @typedef {Object.<string, (val: any) => *>} ExtendObjType */
/** @typedef {Array<[string, (val: any) => *]>} ExtendObjTypeArray */
/**
* Adds new type checkers to the typeValidator without overwriting existing ones.
*
* Accepts either an object with named functions or an array of [key, fn] arrays.
* If no index is provided, the type is inserted just before 'object' (if it exists), or at the end.
*
* @param {ExtendObjType|ExtendObjTypeArray} newItems
* - New type validators to be added.
* @param {number} [index] - Optional. Position at which to insert each new type. Ignored if the type already exists.
* @returns {string[]} - A list of successfully added type names.
*
* @example
* extendObjType({
* htmlElement2: val => typeof HTMLElement !== 'undefined' && val instanceof HTMLElement
* });
*
* @example
* extendObjType([
* ['alpha', val => typeof val === 'string'],
* ['beta', val => Array.isArray(val)]
* ]);
*/
export function extendObjType(newItems, index) {
const added = [];
const entries = Array.isArray(newItems) ? newItems : Object.entries(newItems);
for (const [key, fn] of entries) {
if (!typeValidator.items.hasOwnProperty(key)) {
// @ts-ignore
typeValidator.items[key] = fn;
let insertAt = typeof index === 'number' ? index : -1; // Default to -1 if index isn't provided
// Default to before 'object', or to the end
if (insertAt === -1) {
const objectIndex = typeValidator.order.indexOf('object');
insertAt = objectIndex > -1 ? objectIndex : typeValidator.order.length;
}
// Ensure insertAt is a valid number and not out of bounds
insertAt = Math.min(Math.max(0, insertAt), typeValidator.order.length);
typeValidator.order.splice(insertAt, 0, key);
added.push(key);
}
}
return added;
}
/**
* Reorders the typeValidator.order array according to a custom new order.
* All values in the new order must already exist in the current order.
* The function does not mutate the original array structure directly.
*
* @param {string[]} newOrder - The new order of type names.
* @returns {boolean} - Returns true if the reorder was successful, false if invalid keys were found.
*
* @example
* reorderObjTypeOrder([
* 'string', 'number', 'array', 'object'
* ]);
*/
export function reorderObjTypeOrder(newOrder) {
const currentOrder = [...typeValidator.order]; // shallow clone
// All keys in newOrder must exist in currentOrder
const isValid = newOrder.every((type) => currentOrder.includes(type));
if (!isValid)
return false;
// Reassign only if valid
typeValidator.order = newOrder.slice(); // assign shallow copy
return true;
}
/**
* Returns a cloned version of the `typeValidator.order` array.
* The cloned array will not be affected by future changes to the original `order`.
*
* @returns {string[]} - A new array with the same values as `typeValidator.order`.
*/
export function cloneObjTypeOrder() {
return [...typeValidator.order]; // Creates a shallow copy of the array
}
/**
* Returns the detected type name of a given value based on predefined type validators.
*
* This function uses `getType` with a predefined `typeValidator` to determine or compare types safely.
* in the specified `typeValidator.order`. The first matching type is returned.
*
* If `val` is `null`, it immediately returns `'null'`.
* If no match is found, it returns `'unknown'`.
*
* @param {any} val - The value whose type should be determined.
* @returns {string} - The type name of the value (e.g., "array", "date", "map"), or "unknown" if no match is found.
*
* @example
* getType([]); // "array"
* getType(null); // "null"
* getType(new Set()); // "set"
* getType(() => {}); // "unknown"
*/
const getType = (val) => {
if (val === null)
return 'null';
// @ts-ignore
for (const name of typeValidator.order) {
// @ts-ignore
if (typeof typeValidator.items[name] !== 'function' || typeValidator.items[name](val))
return name;
}
return 'unknown';
};
/**
* Checks the type of a given object or returns its type as a string.
*
* @param {*} obj - The object to check or identify.
* @param {string} [type] - Optional. If provided, checks whether the object matches this type (e.g., "object", "array", "string").
* @returns {boolean|string|null} - Returns `true` if the type matches, `false` if not,
* the type string if no type is provided, or `null` if the object is `undefined`.
*
* @example
* objType([], 'array'); // true
* objType({}, 'object'); // true
* objType('hello'); // "string"
* objType(undefined); // null
*/
export function objType(obj, type) {
if (typeof obj === 'undefined')
return null;
const result = getType(obj);
if (typeof type === 'string')
return result === type.toLowerCase();
return result;
}
/**
* Checks the type of a given object and returns the validation value if a known type is detected.
*
* @param {*} obj - The object to check or identify.
* @returns {{ valid:*; type: string | null }} - Returns the type result.
*/
export function checkObj(obj) {
/** @type {{ valid:*; type: string | null }} */
const data = { valid: null, type: null };
for (const name of typeValidator.order) {
// @ts-ignore
if (typeof typeValidator.items[name] === 'function') {
// @ts-ignore
const result = typeValidator.items[name](obj);
if (result) {
data.valid = result;
data.type = name;
break;
}
}
}
return data;
}
/**
* Creates a clone of the functions from the `typeValidator` object.
* It returns a new object where the keys are the same and the values are the cloned functions.
*/
export function getCheckObj() {
return Object.fromEntries(Object.entries(typeValidator.items).map(([key, fn]) => [key, fn]));
}
// Insert obj types
extendObjType([
[
'undefined',
/** @param {*} val @returns {val is undefined} */
(val) => typeof val === 'undefined',
],
[
'null',
/** @param {*} val @returns {val is null} */
(val) => val === null,
],
[
'boolean',
/** @param {*} val @returns {val is boolean} */
(val) => typeof val === 'boolean',
],
[
'number',
/** @param {*} val @returns {val is number} */
(val) => typeof val === 'number' && !Number.isNaN(val),
],
[
'bigint',
/** @param {*} val @returns {val is bigint} */
(val) => typeof val === 'bigint',
],
[
'string',
/** @param {*} val @returns {val is string} */
(val) => typeof val === 'string',
],
[
'symbol',
/** @param {*} val @returns {val is symbol} */
(val) => typeof val === 'symbol',
],
[
'function',
/** @param {*} val @returns {val is Function} */
(val) => typeof val === 'function',
],
[
'array',
/** @param {*} val @returns {val is any[]} */
(val) => Array.isArray(val),
],
]);
if (!isBrowser) {
extendObjType([
[
'buffer',
/** @param {*} val @returns {val is Buffer} */
(val) => typeof Buffer !== 'undefined' && Buffer.isBuffer(val),
],
]);
}
if (isBrowser) {
extendObjType([
[
'file',
/** @param {*} val @returns {val is File} */
(val) => typeof File !== 'undefined' && val instanceof File,
],
]);
}
extendObjType([
[
'date',
/** @param {*} val @returns {val is Date} */
(val) => val instanceof Date,
],
[
'regexp',
/** @param {*} val @returns {val is RegExp} */
(val) => val instanceof RegExp,
],
[
'map',
/** @param {*} val @returns {val is Map<unknown, unknown>} */
(val) => val instanceof Map,
],
[
'set',
/** @param {*} val @returns {val is Set<unknown>} */
(val) => val instanceof Set,
],
[
'weakmap',
/** @param {*} val @returns {val is WeakMap<unknown, unknown>} */
(val) => val instanceof WeakMap,
],
[
'weakset',
/** @param {*} val @returns {val is WeakSet<unknown>} */
(val) => val instanceof WeakSet,
],
[
'promise',
/** @param {*} val @returns {val is Promise<unknown>} */
(val) => val instanceof Promise,
],
]);
if (isBrowser) {
extendObjType([
[
'htmlelement',
/** @param {*} val @returns {val is HTMLElement} */
(val) => typeof HTMLElement !== 'undefined' && val instanceof HTMLElement,
],
]);
}
extendObjType([
[
'object',
/** @param {*} val @returns {val is Record<string | number | symbol, unknown>} */
(val) => isJsonObject(val),
],
]);