/* IMPORT */ import { debounce } from 'dettle'; import fs from 'node:fs'; import path from 'node:path'; import sfs from 'stubborn-fs'; import readdir from 'tiny-readdir'; import { POLLING_TIMEOUT } from './constants.js'; /* MAIN */ const Utils = { /* LANG API */ lang: { debounce, attempt: (fn) => { try { return fn(); } catch (error) { return Utils.lang.castError(error); } }, castArray: (x) => { return Utils.lang.isArray(x) ? x : [x]; }, castError: (exception) => { if (Utils.lang.isError(exception)) return exception; if (Utils.lang.isString(exception)) return new Error(exception); return new Error('Unknown error'); }, defer: (callback) => { return setTimeout(callback, 0); }, isArray: (value) => { return Array.isArray(value); }, isError: (value) => { return value instanceof Error; }, isFunction: (value) => { return typeof value === 'function'; }, isNaN: (value) => { return Number.isNaN(value); }, isNumber: (value) => { return typeof value === 'number'; }, isPrimitive: (value) => { if (value === null) return true; const type = typeof value; return type !== 'object' && type !== 'function'; }, isShallowEqual: (x, y) => { if (x === y) return true; if (Utils.lang.isNaN(x)) return Utils.lang.isNaN(y); if (Utils.lang.isPrimitive(x) || Utils.lang.isPrimitive(y)) return x === y; for (const i in x) if (!(i in y)) return false; for (const i in y) if (x[i] !== y[i]) return false; return true; }, isSet: (value) => { return value instanceof Set; }, isString: (value) => { return typeof value === 'string'; }, isUndefined: (value) => { return value === undefined; }, noop: () => { return; }, uniq: (arr) => { if (arr.length < 2) return arr; return Array.from(new Set(arr)); } }, /* FS API */ fs: { getDepth: (targetPath) => { return Math.max(0, targetPath.split(path.sep).length - 1); }, getRealPath: (targetPath, native) => { try { return native ? fs.realpathSync.native(targetPath) : fs.realpathSync(targetPath); } catch { return; } }, isSubPath: (targetPath, subPath) => { return (subPath.startsWith(targetPath) && subPath[targetPath.length] === path.sep && (subPath.length - targetPath.length) > path.sep.length); }, poll: (targetPath, timeout = POLLING_TIMEOUT) => { return sfs.retry.stat(timeout)(targetPath, { bigint: true }).catch(Utils.lang.noop); }, readdir: async (rootPath, ignore, depth = Infinity, limit = Infinity, signal, readdirMap) => { if (readdirMap && depth === 1 && rootPath in readdirMap) { // Reusing cached data const result = readdirMap[rootPath]; return [result.directories, result.files]; } else { // Retrieving fresh data const result = await readdir(rootPath, { depth, limit, ignore, signal }); return [result.directories, result.files]; } } } }; /* EXPORT */ export default Utils;