{"_id":"@ahmetilhn/handy-utils","_rev":"12-4ded8c443f47414ef4498a8469621c81","name":"@ahmetilhn/handy-utils","dist-tags":{"latest":"4.2.0"},"versions":{"3.2.0":{"name":"@ahmetilhn/handy-utils","version":"3.2.0","keywords":["javascript","utility","library","helpers","tools","functions","development","lodash","utils","isClient","isServer","isNumber","isDefined","isUndefined","isFunction","isArray","isObject","isDate","sleep"],"author":{"name":"Ahmet ilhan","email":"ahmetilhan.dev@gmail.com"},"license":"MIT","_id":"@ahmetilhn/handy-utils@3.2.0","maintainers":[{"name":"ahmetilhn","email":"ahmetilhan.dev@gmail.com"}],"homepage":"https://github.com/ahmetilhn/amigo-js#readme","bugs":{"url":"https://github.com/ahmetilhn/amigo-js/issues"},"dist":{"shasum":"6cb8418fa86ed5da9dc16e1d86d38a2a03479424","tarball":"https://registry.npmjs.org/@ahmetilhn/handy-utils/-/handy-utils-3.2.0.tgz","fileCount":5,"integrity":"sha512-0hutLp65xjgD3SZKt3cNoYuFrHJc+mUUnWppF8mz+B14e4cxbAvCMs8rGT0kPpzufZfvyhiib0NV8H2YWHfRAg==","signatures":[{"sig":"MEQCIAajc5MCKYdpmU41pZziXC+x0Ym+kHXks1QT+HENDTenAiAPMGA0stXVJ8+XlLh0UqO5LT1kGTnfJ1JhdRtA/4I0rg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":10405},"main":"build/index.js","types":"build/index.d.ts","gitHead":"e2ca85a0ea61a736c96afa03425c2cc34458bcbb","private":false,"scripts":{"dev":"sh ./scripts/dev-build.sh","test":"jest","build":"sh ./scripts/prod-build.sh"},"_npmUser":{"name":"ahmetilhn","email":"ahmetilhan.dev@gmail.com"},"publisher":"Ahmet ilhan","repository":{"url":"git+https://github.com/ahmetilhn/amigo-js.git","type":"git"},"_npmVersion":"9.8.1","description":"Handy utils offers developers a powerful and easy-to-use toolset. With its clean, modular and scalable code structure, it accelerates development processes and reduces code complexity. With a wide range of functions, it offers ready-made methods for solvi","directories":{},"_nodeVersion":"18.18.2","_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","tslib":"^2.7.0","rollup":"^4.21.0","babel-jest":"^29.7.0","@babel/core":"^7.25.2","@types/jest":"^29.5.12","@babel/preset-env":"^7.25.4","rollup-plugin-dts":"^6.1.1","@rollup/plugin-babel":"^6.0.4","@rollup/plugin-terser":"^0.4.4","jest-environment-jsdom":"^29.7.0","@babel/preset-typescript":"^7.24.7","@rollup/plugin-typescript":"^11.1.6","@rollup/plugin-node-resolve":"^15.2.3"},"_npmOperationalInternal":{"tmp":"tmp/handy-utils_3.2.0_1741794646757_0.6701194356934248","host":"s3://npm-registry-packages-npm-production"}},"3.2.1":{"name":"@ahmetilhn/handy-utils","version":"3.2.1","keywords":["javascript","utility","library","helpers","tools","functions","development","lodash","utils","isClient","isServer","isNumber","isDefined","isUndefined","isFunction","isArray","isObject","isDate","sleep"],"author":{"name":"Ahmet ilhan","email":"ahmetilhan.dev@gmail.com"},"license":"MIT","_id":"@ahmetilhn/handy-utils@3.2.1","maintainers":[{"name":"ahmetilhn","email":"ahmetilhan.dev@gmail.com"}],"homepage":"https://github.com/ahmetilhn/amigo-js#readme","bugs":{"url":"https://github.com/ahmetilhn/amigo-js/issues"},"dist":{"shasum":"72029e2231d4e6bd71c8d8cd546c1c2ea32cb8b9","tarball":"https://registry.npmjs.org/@ahmetilhn/handy-utils/-/handy-utils-3.2.1.tgz","fileCount":5,"integrity":"sha512-FRpLDIkiq1owrvC2pPYHQzH4EaRZ0sAmuqNB2XWADNNHC+r8jSCSk8UkA098vo1m6L99mf92za94g7p7BLib2A==","signatures":[{"sig":"MEQCIF4MbMHLbsML25WqUd8GJgg4QkUjZ9modycFXgoeRt4MAiBgLYkBS0yA4GVfJ1pLyzZ3YE6/9X2mTpF5Lx1LdWRtNA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":10393},"main":"build/index.js","types":"build/index.d.ts","gitHead":"734e98501e47409fbb139920dcfced74e8f2ea33","private":false,"scripts":{"dev":"sh ./scripts/dev-build.sh","test":"jest","build":"sh ./scripts/prod-build.sh"},"_npmUser":{"name":"ahmetilhn","email":"ahmetilhan.dev@gmail.com"},"publisher":"Ahmet ilhan","repository":{"url":"git+https://github.com/ahmetilhn/amigo-js.git","type":"git"},"_npmVersion":"9.8.1","description":"Handy utils offers developers a powerful and easy-to-use toolset. With its clean, modular and scalable code structure, it accelerates development processes and reduces code complexity. With a wide range of functions, it offers ready-made methods for solvi","directories":{},"_nodeVersion":"18.18.2","_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","tslib":"^2.7.0","rollup":"^4.21.0","babel-jest":"^29.7.0","@babel/core":"^7.25.2","@types/jest":"^29.5.12","@babel/preset-env":"^7.25.4","rollup-plugin-dts":"^6.1.1","@rollup/plugin-babel":"^6.0.4","@rollup/plugin-terser":"^0.4.4","jest-environment-jsdom":"^29.7.0","@babel/preset-typescript":"^7.24.7","@rollup/plugin-typescript":"^11.1.6","@rollup/plugin-node-resolve":"^15.2.3"},"_npmOperationalInternal":{"tmp":"tmp/handy-utils_3.2.1_1742506968527_0.8277259516614983","host":"s3://npm-registry-packages-npm-production"}},"3.2.2":{"name":"@ahmetilhn/handy-utils","version":"3.2.2","keywords":["javascript","utility","library","helpers","tools","functions","development","lodash","utils","isClient","isServer","isNumber","isDefined","isUndefined","isFunction","isArray","isObject","isDate","sleep"],"author":{"name":"Ahmet ilhan","email":"ahmetilhan.dev@gmail.com"},"license":"MIT","_id":"@ahmetilhn/handy-utils@3.2.2","maintainers":[{"name":"ahmetilhn","email":"ahmetilhan.dev@gmail.com"}],"homepage":"https://github.com/ahmetilhn/amigo-js#readme","bugs":{"url":"https://github.com/ahmetilhn/amigo-js/issues"},"dist":{"shasum":"9d8b2c260f2c39c04b7a74991ea78579a27b999e","tarball":"https://registry.npmjs.org/@ahmetilhn/handy-utils/-/handy-utils-3.2.2.tgz","fileCount":5,"integrity":"sha512-GUThiVMmOdrCcjZuI9KOSfrmDO5mwj4KduYnExrMcI0C7VD/ItK1t1EOVIM23QcHZiDwazcavTQOQFqbV3xjMg==","signatures":[{"sig":"MEYCIQDpgwr8G0ucT4NOCksocB8l9pNJTGO8steFwt1SD4M6XQIhAK2XyTVPki6hNC+2uhkEwtEaS+2dHbVPIGn++TiD/i9D","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":10756},"main":"build/index.js","types":"build/index.d.ts","gitHead":"27e0a17b2f70bdde6fe2fb22b25a14b01c7ac084","private":false,"scripts":{"dev":"sh ./scripts/dev-build.sh","test":"jest","build":"sh ./scripts/prod-build.sh"},"_npmUser":{"name":"ahmetilhn","email":"ahmetilhan.dev@gmail.com"},"publisher":"Ahmet ilhan","repository":{"url":"git+https://github.com/ahmetilhn/amigo-js.git","type":"git"},"_npmVersion":"9.8.1","description":"Handy utils offers developers a powerful and easy-to-use toolset. With its clean, modular and scalable code structure, it accelerates development processes and reduces code complexity. With a wide range of functions, it offers ready-made methods for solvi","directories":{},"_nodeVersion":"18.18.2","_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","tslib":"^2.7.0","rollup":"^4.21.0","babel-jest":"^29.7.0","@babel/core":"^7.25.2","@types/jest":"^29.5.12","@babel/preset-env":"^7.25.4","rollup-plugin-dts":"^6.1.1","@rollup/plugin-babel":"^6.0.4","@rollup/plugin-terser":"^0.4.4","jest-environment-jsdom":"^29.7.0","@babel/preset-typescript":"^7.24.7","@rollup/plugin-typescript":"^11.1.6","@rollup/plugin-node-resolve":"^15.2.3"},"_npmOperationalInternal":{"tmp":"tmp/handy-utils_3.2.2_1742507111691_0.04197456014258649","host":"s3://npm-registry-packages-npm-production"}},"3.2.3":{"name":"@ahmetilhn/handy-utils","version":"3.2.3","keywords":["javascript","utility","library","helpers","tools","functions","development","lodash","utils","isClient","isServer","isNumber","isDefined","isUndefined","isFunction","isArray","isObject","isDate","sleep"],"author":{"name":"Ahmet ilhan","email":"ahmetilhan.dev@gmail.com"},"license":"MIT","_id":"@ahmetilhn/handy-utils@3.2.3","maintainers":[{"name":"ahmetilhn","email":"ahmetilhan.dev@gmail.com"}],"homepage":"https://github.com/ahmetilhn/amigo-js#readme","bugs":{"url":"https://github.com/ahmetilhn/amigo-js/issues"},"dist":{"shasum":"8a2bf1d53d4f94b6d28bd7768863ae5551dcde04","tarball":"https://registry.npmjs.org/@ahmetilhn/handy-utils/-/handy-utils-3.2.3.tgz","fileCount":5,"integrity":"sha512-tHHIcfEjzj/dSNhGaHlY39bZi5xyL6QUgovVP5wAC06d/aEEwRpChl2UKYFhurtQr7aQ1XlAHJwXLK2uHvPD/Q==","signatures":[{"sig":"MEYCIQD5CJXn1i2qMd77NJPu1IuAlsyoF1PFmIb4odC/Fko7TQIhAJOTL4ue9qrHrSPDHjYGeeU/uio8EU2cBd+VZYhJEiRJ","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":10863},"main":"build/index.js","types":"build/index.d.ts","gitHead":"bcbd3c04eca6a34930fd9f7466849853db07fd91","private":false,"scripts":{"dev":"sh ./scripts/dev-build.sh","test":"jest","build":"sh ./scripts/prod-build.sh"},"_npmUser":{"name":"ahmetilhn","email":"ahmetilhan.dev@gmail.com"},"publisher":"Ahmet ilhan","repository":{"url":"git+https://github.com/ahmetilhn/amigo-js.git","type":"git"},"_npmVersion":"9.8.1","description":"Handy utils offers developers a powerful and easy-to-use toolset. With its clean, modular and scalable code structure, it accelerates development processes and reduces code complexity. With a wide range of functions, it offers ready-made methods for solvi","directories":{},"_nodeVersion":"18.18.2","_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","tslib":"^2.7.0","rollup":"^4.21.0","babel-jest":"^29.7.0","@babel/core":"^7.25.2","@types/jest":"^29.5.12","@babel/preset-env":"^7.25.4","rollup-plugin-dts":"^6.1.1","@rollup/plugin-babel":"^6.0.4","@rollup/plugin-terser":"^0.4.4","jest-environment-jsdom":"^29.7.0","@babel/preset-typescript":"^7.24.7","@rollup/plugin-typescript":"^11.1.6","@rollup/plugin-node-resolve":"^15.2.3"},"_npmOperationalInternal":{"tmp":"tmp/handy-utils_3.2.3_1744227367328_0.17427505946809307","host":"s3://npm-registry-packages-npm-production"}},"3.2.4":{"name":"@ahmetilhn/handy-utils","version":"3.2.4","keywords":["javascript","utility","library","helpers","tools","functions","development","lodash","utils","isClient","isServer","isNumber","isDefined","isUndefined","isFunction","isArray","isObject","isDate","sleep"],"author":{"name":"Ahmet ilhan","email":"ahmetilhan.dev@gmail.com"},"license":"MIT","_id":"@ahmetilhn/handy-utils@3.2.4","maintainers":[{"name":"ahmetilhn","email":"ahmetilhan.dev@gmail.com"}],"homepage":"https://github.com/ahmetilhn/handy-utils#readme","bugs":{"url":"https://github.com/ahmetilhn/handy-utils/issues"},"dist":{"shasum":"4aeb055525e6b0683010e9eb49362848991c921a","tarball":"https://registry.npmjs.org/@ahmetilhn/handy-utils/-/handy-utils-3.2.4.tgz","fileCount":5,"integrity":"sha512-5xQ3OR3BDmjzN7zA6BnEb4Vuo8JveUJErDgqN7tEYqT48Ywc61mHxKt+UzXCHP1YFQQio6ABF+QEgitJVWVX5g==","signatures":[{"sig":"MEUCIAdb8dmL/ZB+ZU2FBICKLszs75+9UVc/WS3F9t8T6jdfAiEAy4ZB+FmzhwnyfBuicc22+49OlcHsfLJXvAgvHyrkZgI=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":10866},"main":"build/index.js","types":"build/index.d.ts","gitHead":"be39481dd40366d305a4a35e46c80ffe48494c11","private":false,"scripts":{"dev":"sh ./scripts/dev-build.sh","test":"jest","build":"sh ./scripts/prod-build.sh"},"_npmUser":{"name":"ahmetilhn","email":"ahmetilhan.dev@gmail.com"},"publisher":"Ahmet ilhan","repository":{"url":"git+https://github.com/ahmetilhn/handy-utils.git","type":"git"},"_npmVersion":"9.8.1","description":"Handy utils offers developers a powerful and easy-to-use toolset. With its clean, modular and scalable code structure, it accelerates development processes and reduces code complexity. With a wide range of functions, it offers ready-made methods for solvi","directories":{},"_nodeVersion":"18.18.2","_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","tslib":"^2.7.0","rollup":"^4.21.0","babel-jest":"^29.7.0","@babel/core":"^7.25.2","@types/jest":"^29.5.12","@babel/preset-env":"^7.25.4","rollup-plugin-dts":"^6.1.1","@rollup/plugin-babel":"^6.0.4","@rollup/plugin-terser":"^0.4.4","jest-environment-jsdom":"^29.7.0","@babel/preset-typescript":"^7.24.7","@rollup/plugin-typescript":"^11.1.6","@rollup/plugin-node-resolve":"^15.2.3"},"_npmOperationalInternal":{"tmp":"tmp/handy-utils_3.2.4_1772189962278_0.24640566723581925","host":"s3://npm-registry-packages-npm-production"}},"3.2.5":{"name":"@ahmetilhn/handy-utils","version":"3.2.5","keywords":["javascript","utility","library","helpers","tools","functions","development","lodash","utils","isClient","isServer","isNumber","isDefined","isUndefined","isFunction","isArray","isObject","isDate","sleep"],"author":{"name":"Ahmet ilhan","email":"ahmetilhan.dev@gmail.com"},"license":"MIT","_id":"@ahmetilhn/handy-utils@3.2.5","maintainers":[{"name":"ahmetilhn","email":"ahmetilhan.dev@gmail.com"}],"homepage":"https://github.com/ahmetilhn/handy-utils#readme","bugs":{"url":"https://github.com/ahmetilhn/handy-utils/issues"},"dist":{"shasum":"ffaeaad5fd83740a5546e0bebc8b0e971d635004","tarball":"https://registry.npmjs.org/@ahmetilhn/handy-utils/-/handy-utils-3.2.5.tgz","fileCount":5,"integrity":"sha512-joe9YCjFJdw+h+rmI984RUfIWngHfGFVtV07pxwLcEsDkm1G53DjNKTAuqBGGKFpT4C7BMK4cx14ezz2cppLpA==","signatures":[{"sig":"MEYCIQDtwCnxf5lEEHmPEydzQMdDIfsqXvPtSf8jy+DeIEzmeAIhAPQFW7GKDYvHcHbPvHdMRIqFCQLFaNusOyZDYP5Uk3IW","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":11344},"main":"build/index.js","types":"build/index.d.ts","gitHead":"fabb6860d31b5890165de064839cd33bbe2f115f","private":false,"scripts":{"dev":"sh ./scripts/dev-build.sh","test":"jest","build":"sh ./scripts/prod-build.sh"},"_npmUser":{"name":"ahmetilhn","email":"ahmetilhan.dev@gmail.com"},"publisher":"Ahmet ilhan","repository":{"url":"git+https://github.com/ahmetilhn/handy-utils.git","type":"git"},"_npmVersion":"9.8.1","description":"Handy utils offers developers a powerful and easy-to-use toolset. With its clean, modular and scalable code structure, it accelerates development processes and reduces code complexity. With a wide range of functions, it offers ready-made methods for solvi","directories":{},"_nodeVersion":"18.18.2","_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","tslib":"^2.7.0","rollup":"^4.21.0","babel-jest":"^29.7.0","@babel/core":"^7.25.2","@types/jest":"^29.5.12","@babel/preset-env":"^7.25.4","rollup-plugin-dts":"^6.1.1","@rollup/plugin-babel":"^6.0.4","@rollup/plugin-terser":"^0.4.4","jest-environment-jsdom":"^29.7.0","@babel/preset-typescript":"^7.24.7","@rollup/plugin-typescript":"^11.1.6","@rollup/plugin-node-resolve":"^15.2.3"},"_npmOperationalInternal":{"tmp":"tmp/handy-utils_3.2.5_1772190087961_0.5993492612541098","host":"s3://npm-registry-packages-npm-production"}},"3.2.6":{"name":"@ahmetilhn/handy-utils","version":"3.2.6","keywords":["javascript","utility","library","helpers","tools","functions","development","lodash","utils","isClient","isServer","isNumber","isDefined","isUndefined","isFunction","isArray","isObject","isDate","sleep"],"author":{"name":"Ahmet ilhan","email":"ahmetilhan.dev@gmail.com"},"license":"MIT","_id":"@ahmetilhn/handy-utils@3.2.6","maintainers":[{"name":"ahmetilhn","email":"ahmetilhan.dev@gmail.com"}],"homepage":"https://github.com/ahmetilhn/handy-utils#readme","bugs":{"url":"https://github.com/ahmetilhn/handy-utils/issues"},"dist":{"shasum":"c6b87414caaceaa9763008679803468710a67d3d","tarball":"https://registry.npmjs.org/@ahmetilhn/handy-utils/-/handy-utils-3.2.6.tgz","fileCount":5,"integrity":"sha512-B3t3CTOy7n0hfXWzuA3+i5KEH90BtRcuT3SUaxuvlHMa7w4a3zfwVrXgVYfA0sydcY8PMrSLe2umyjrUKQVrhQ==","signatures":[{"sig":"MEQCIAH+dvXM0PWR8O8OdQO7IqDeNfXcUbacjCjM6+IHBpT9AiAezCB/MSpmDHaA8TXtSgv9cwiNGfMwE/NI2pzoh4Ic0g==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":11689},"main":"build/index.js","types":"build/index.d.ts","gitHead":"7e2e83be078513440eeb0ed1366a1d5d9d548aa2","private":false,"scripts":{"dev":"sh ./scripts/dev-build.sh","test":"jest","build":"sh ./scripts/prod-build.sh"},"_npmUser":{"name":"ahmetilhn","email":"ahmetilhan.dev@gmail.com"},"publisher":"Ahmet ilhan","repository":{"url":"git+https://github.com/ahmetilhn/handy-utils.git","type":"git"},"_npmVersion":"9.8.1","description":"Handy utils offers developers a powerful and easy-to-use toolset. With its clean, modular and scalable code structure, it accelerates development processes and reduces code complexity. With a wide range of functions, it offers ready-made methods for solvi","directories":{},"_nodeVersion":"18.18.2","_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","tslib":"^2.7.0","rollup":"^4.21.0","babel-jest":"^29.7.0","@babel/core":"^7.25.2","@types/jest":"^29.5.12","@babel/preset-env":"^7.25.4","rollup-plugin-dts":"^6.1.1","@rollup/plugin-babel":"^6.0.4","@rollup/plugin-terser":"^0.4.4","jest-environment-jsdom":"^29.7.0","@babel/preset-typescript":"^7.24.7","@rollup/plugin-typescript":"^11.1.6","@rollup/plugin-node-resolve":"^15.2.3"},"_npmOperationalInternal":{"tmp":"tmp/handy-utils_3.2.6_1773914556747_0.4051578684404096","host":"s3://npm-registry-packages-npm-production"}},"3.2.7":{"name":"@ahmetilhn/handy-utils","version":"3.2.7","keywords":["javascript","utility","library","helpers","tools","functions","development","lodash","utils","isClient","isServer","isNumber","isDefined","isUndefined","isFunction","isArray","isObject","isDate","sleep"],"author":{"name":"Ahmet ilhan","email":"ahmetilhan.dev@gmail.com"},"license":"MIT","_id":"@ahmetilhn/handy-utils@3.2.7","maintainers":[{"name":"ahmetilhn","email":"ahmetilhan.dev@gmail.com"}],"homepage":"https://github.com/ahmetilhn/handy-utils#readme","bugs":{"url":"https://github.com/ahmetilhn/handy-utils/issues"},"dist":{"shasum":"bc4fff61f71b47182f8156be3503421d3455619f","tarball":"https://registry.npmjs.org/@ahmetilhn/handy-utils/-/handy-utils-3.2.7.tgz","fileCount":5,"integrity":"sha512-kcZVbJwRo8P/Mx3WqbUBSewGLUlJ9quicjSF4waixZxmEhmy3ocEpAc5w4qAVtygzVzD2PkMQWBQFx4U7mu1/Q==","signatures":[{"sig":"MEYCIQChRf7r1CQVoPZpHEwsucmLjgHIU0WU124kyi3j2EHJFAIhANjkjpe7tcsB8kN40Oz4AV+7oyj9DQl1nCYu5n1mb8nf","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":12028},"main":"build/index.js","types":"build/index.d.ts","gitHead":"5bb49e448a1a44c7c66866577e0d23c94f0a51e4","private":false,"scripts":{"dev":"sh ./scripts/dev-build.sh","test":"jest","build":"sh ./scripts/prod-build.sh"},"_npmUser":{"name":"ahmetilhn","email":"ahmetilhan.dev@gmail.com"},"publisher":"Ahmet ilhan","repository":{"url":"git+https://github.com/ahmetilhn/handy-utils.git","type":"git"},"_npmVersion":"9.8.1","description":"Handy utils offers developers a powerful and easy-to-use toolset. With its clean, modular and scalable code structure, it accelerates development processes and reduces code complexity. With a wide range of functions, it offers ready-made methods for solvi","directories":{},"_nodeVersion":"18.18.2","_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","tslib":"^2.7.0","rollup":"^4.21.0","babel-jest":"^29.7.0","@babel/core":"^7.25.2","@types/jest":"^29.5.12","@babel/preset-env":"^7.25.4","rollup-plugin-dts":"^6.1.1","@rollup/plugin-babel":"^6.0.4","@rollup/plugin-terser":"^0.4.4","jest-environment-jsdom":"^29.7.0","@babel/preset-typescript":"^7.24.7","@rollup/plugin-typescript":"^11.1.6","@rollup/plugin-node-resolve":"^15.2.3"},"_npmOperationalInternal":{"tmp":"tmp/handy-utils_3.2.7_1774192204609_0.4044832121586359","host":"s3://npm-registry-packages-npm-production"}},"3.2.8":{"name":"@ahmetilhn/handy-utils","version":"3.2.8","keywords":["javascript","utility","library","helpers","tools","functions","development","lodash","utils","isClient","isServer","isNumber","isDefined","isUndefined","isFunction","isArray","isObject","isDate","sleep"],"author":{"name":"Ahmet ilhan","email":"ahmetilhan.dev@gmail.com"},"license":"MIT","_id":"@ahmetilhn/handy-utils@3.2.8","maintainers":[{"name":"ahmetilhn","email":"ahmetilhan.dev@gmail.com"}],"homepage":"https://github.com/ahmetilhn/handy-utils#readme","bugs":{"url":"https://github.com/ahmetilhn/handy-utils/issues"},"dist":{"shasum":"b4d615d1e558f127fd0ca83e342a6117613e7c60","tarball":"https://registry.npmjs.org/@ahmetilhn/handy-utils/-/handy-utils-3.2.8.tgz","fileCount":5,"integrity":"sha512-vvpKe13MtysBWvB3VPTQHb1jYmx5Ueknl1WQe4B/1QemkPB0HRIjfX6gNfphwG8wQK1b3c7CDvXI2WiZr1Qmng==","signatures":[{"sig":"MEUCIQCYAVy0Bh3BOfhfF14QYA8SrGB495gq1oOX7gGgO1UlQAIgFDnEXXZpe2q8qeOu68qsc5rysyWaIxF/oKLgHCBJdxI=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":12029},"main":"build/index.js","types":"build/index.d.ts","gitHead":"50f7417bbc5f26214fbeac3394257d7f6b91b168","private":false,"scripts":{"dev":"sh ./scripts/dev-build.sh","test":"jest","build":"sh ./scripts/prod-build.sh"},"_npmUser":{"name":"ahmetilhn","email":"ahmetilhan.dev@gmail.com"},"publisher":"Ahmet ilhan","repository":{"url":"git+https://github.com/ahmetilhn/handy-utils.git","type":"git"},"_npmVersion":"9.8.1","description":"Handy utils offers developers a powerful and easy-to-use toolset. With its clean, modular and scalable code structure, it accelerates development processes and reduces code complexity. With a wide range of functions, it offers ready-made methods for solvi","directories":{},"_nodeVersion":"18.18.2","_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","tslib":"^2.7.0","rollup":"^4.21.0","babel-jest":"^29.7.0","@babel/core":"^7.25.2","@types/jest":"^29.5.12","@babel/preset-env":"^7.25.4","rollup-plugin-dts":"^6.1.1","@rollup/plugin-babel":"^6.0.4","@rollup/plugin-terser":"^0.4.4","jest-environment-jsdom":"^29.7.0","@babel/preset-typescript":"^7.24.7","@rollup/plugin-typescript":"^11.1.6","@rollup/plugin-node-resolve":"^15.2.3"},"_npmOperationalInternal":{"tmp":"tmp/handy-utils_3.2.8_1774196690778_0.6502789496976156","host":"s3://npm-registry-packages-npm-production"}},"4.0.0":{"name":"@ahmetilhn/handy-utils","version":"4.0.0","keywords":["javascript","utility","library","helpers","tools","functions","development","lodash","utils","isClient","isServer","isNumber","isDefined","isUndefined","isFunction","isArray","isObject","isDate","sleep"],"author":{"name":"Ahmet ilhan","email":"ahmetilhan.dev@gmail.com"},"license":"MIT","_id":"@ahmetilhn/handy-utils@4.0.0","maintainers":[{"name":"ahmetilhn","email":"ahmetilhan.dev@gmail.com"}],"homepage":"https://github.com/ahmetilhn/handy-utils#readme","bugs":{"url":"https://github.com/ahmetilhn/handy-utils/issues"},"dist":{"shasum":"345d4d0e6ce0caab14ec88a04b8b67348842cb30","tarball":"https://registry.npmjs.org/@ahmetilhn/handy-utils/-/handy-utils-4.0.0.tgz","fileCount":6,"integrity":"sha512-pjAph7wlMh1MVTKlWk4hSG7gFCCtdSS3FvIk0wzhUk0xZ3eesvvs1iupSmLjBHpSgsO//Jb+sWSXAX99e/oreQ==","signatures":[{"sig":"MEUCIQC4Z632ZvZhqz2DAT6SklNexaixr74VNlrGRUlfdnDlTQIgahlDURGGGHu/ZtNEvK1no/BVFoDBldY0CmDQ/lhX5TQ=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@ahmetilhn%2fhandy-utils@4.0.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":53176},"main":"build/index.js","types":"build/index.d.ts","engines":{"node":">=24"},"gitHead":"eac4242780e6cf12861c012e6c1b0bc0da698a0d","private":false,"scripts":{"dev":"sh ./scripts/dev-build.sh","test":"jest","build":"sh ./scripts/prod-build.sh","verify":"npm run typecheck && npm run test && npm run build","typecheck":"tsc --noEmit -p tsconfig.json","prepublishOnly":"npm run verify"},"_npmUser":{"name":"ahmetilhn","email":"ahmetilhan.dev@gmail.com"},"publisher":"Ahmet ilhan","repository":{"url":"git+https://github.com/ahmetilhn/handy-utils.git","type":"git"},"_npmVersion":"11.17.0","description":"Handy utils offers developers a powerful and easy-to-use toolset. With its clean, modular and scalable code structure, it accelerates development processes and reduces code complexity. With a wide range of functions, it offers ready-made methods for solvi","directories":{},"sideEffects":false,"_nodeVersion":"24.19.0","_hasShrinkwrap":false,"devDependencies":{"jest":"^30.4.2","tslib":"^2.8.1","rollup":"^4.62.4","babel-jest":"^30.4.1","typescript":"^6.0.3","@babel/core":"^8.0.1","@types/jest":"^30.0.0","@types/node":"^26.2.0","@babel/preset-env":"^8.0.2","rollup-plugin-dts":"^6.5.1","@rollup/plugin-terser":"^1.0.0","jest-environment-jsdom":"^30.4.1","@babel/preset-typescript":"^8.0.1","@rollup/plugin-typescript":"^12.3.0"},"_npmOperationalInternal":{"tmp":"tmp/handy-utils_4.0.0_1786821735463_0.10524068596642722","host":"s3://npm-registry-packages-npm-production"}},"4.1.0":{"name":"@ahmetilhn/handy-utils","version":"4.1.0","keywords":["javascript","utility","library","helpers","tools","functions","development","lodash","utils","isClient","isServer","isNumber","isDefined","isUndefined","isFunction","isArray","isObject","isDate","sleep"],"author":{"name":"Ahmet ilhan","email":"ahmetilhan.dev@gmail.com"},"license":"MIT","_id":"@ahmetilhn/handy-utils@4.1.0","maintainers":[{"name":"ahmetilhn","email":"ahmetilhan.dev@gmail.com"}],"homepage":"https://github.com/ahmetilhn/handy-utils#readme","bugs":{"url":"https://github.com/ahmetilhn/handy-utils/issues"},"dist":{"shasum":"74365e1e960deea028ac5a670db59a1cac31c9b5","tarball":"https://registry.npmjs.org/@ahmetilhn/handy-utils/-/handy-utils-4.1.0.tgz","fileCount":6,"integrity":"sha512-j0i0sHKfQz2Uyfiwx8K5s3eDeladZlmmPjV0ZGhgDdbM2I2Q7yoex/jmAm6W+pxrPUbkNcUu5jF/60rfu8b/dw==","signatures":[{"sig":"MEUCIAJi8LCxtsymh+QEztjmfjfZgXsG1/cnSQYK+a7b0DGSAiEAswjptgjYNK9RVI3Yf4OKL4jzqH8uety0TyR47cAxU9Y=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@ahmetilhn%2fhandy-utils@4.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":72069},"main":"build/index.js","types":"build/index.d.ts","engines":{"node":">=24"},"gitHead":"53bd847005e2a320785892bc613bfe4a235a8eea","private":false,"scripts":{"dev":"sh ./scripts/dev-build.sh","test":"jest","build":"sh ./scripts/prod-build.sh","verify":"npm run typecheck && npm run test && npm run build","typecheck":"tsc --noEmit -p tsconfig.json","prepublishOnly":"npm run verify"},"_npmUser":{"name":"ahmetilhn","email":"ahmetilhan.dev@gmail.com"},"publisher":"Ahmet ilhan","repository":{"url":"git+https://github.com/ahmetilhn/handy-utils.git","type":"git"},"_npmVersion":"11.17.0","description":"Handy utils offers developers a powerful and easy-to-use toolset. With its clean, modular and scalable code structure, it accelerates development processes and reduces code complexity. With a wide range of functions, it offers ready-made methods for solvi","directories":{},"sideEffects":false,"_nodeVersion":"24.19.0","_hasShrinkwrap":false,"devDependencies":{"jest":"^30.4.2","tslib":"^2.8.1","rollup":"^4.62.4","babel-jest":"^30.4.1","typescript":"^6.0.3","@babel/core":"^8.0.1","@types/jest":"^30.0.0","@types/node":"^26.2.0","@babel/preset-env":"^8.0.2","rollup-plugin-dts":"^6.5.1","@rollup/plugin-terser":"^1.0.0","jest-environment-jsdom":"^30.4.1","@babel/preset-typescript":"^8.0.1","@rollup/plugin-typescript":"^12.3.0"},"_npmOperationalInternal":{"tmp":"tmp/handy-utils_4.1.0_1786824024494_0.12536829216349488","host":"s3://npm-registry-packages-npm-production"}},"4.2.0":{"name":"@ahmetilhn/handy-utils","version":"4.2.0","description":"Handy utils offers developers a powerful and easy-to-use toolset. With its clean, modular and scalable code structure, it accelerates development processes and reduces code complexity. With a wide range of functions, it offers ready-made methods for solvi","author":{"name":"Ahmet ilhan","email":"ahmetilhan.dev@gmail.com"},"license":"MIT","main":"build/index.js","types":"build/index.d.ts","publisher":"Ahmet ilhan","repository":{"type":"git","url":"git+https://github.com/ahmetilhn/handy-utils.git"},"scripts":{"build":"sh ./scripts/prod-build.sh","dev":"sh ./scripts/dev-build.sh","test":"jest","typecheck":"tsc --noEmit -p tsconfig.json","verify":"npm run typecheck && npm run test && npm run build","prepublishOnly":"npm run verify"},"sideEffects":false,"private":false,"keywords":["javascript","utility","library","helpers","tools","functions","development","lodash","utils","isClient","isServer","isNumber","isDefined","isUndefined","isFunction","isArray","isObject","isDate","sleep","debounce"],"devDependencies":{"@babel/core":"^8.0.1","@babel/preset-env":"^8.0.2","@babel/preset-typescript":"^8.0.1","@rollup/plugin-terser":"^1.0.0","@rollup/plugin-typescript":"^12.3.0","@types/jest":"^30.0.0","@types/node":"^26.2.0","babel-jest":"^30.4.1","jest":"^30.4.2","jest-environment-jsdom":"^30.4.1","rollup":"^4.62.4","rollup-plugin-dts":"^6.5.1","tslib":"^2.8.1","typescript":"^6.0.3"},"engines":{"node":">=24"},"gitHead":"83b2ab0c8f112b8df9d9994d4f36ce1c305349c0","_id":"@ahmetilhn/handy-utils@4.2.0","bugs":{"url":"https://github.com/ahmetilhn/handy-utils/issues"},"homepage":"https://github.com/ahmetilhn/handy-utils#readme","_nodeVersion":"24.19.0","_npmVersion":"11.17.0","dist":{"integrity":"sha512-BWxzKgDXZo1Ihtb2ZIY8+fICqjRJkT+ABXVzyu2SqFSRHI6DTrgoCGAHMy8ghi3cXD4XZ1snDsd+PshWb8PF9Q==","shasum":"9d42a208381d45deacaf3078929098740b69d03b","tarball":"https://registry.npmjs.org/@ahmetilhn/handy-utils/-/handy-utils-4.2.0.tgz","fileCount":6,"unpackedSize":82579,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@ahmetilhn%2fhandy-utils@4.2.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIH2qL8Q+HFEsPypLgwnv4XrD+r5wrVn7IIlUnDVlxTr8AiBQ7+eBttIa23tgXFmq/OCtufZQOtzBJ0K72hn4c3CRRw=="}]},"_npmUser":{"name":"ahmetilhn","email":"ahmetilhan.dev@gmail.com"},"directories":{},"maintainers":[{"name":"ahmetilhn","email":"ahmetilhan.dev@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/handy-utils_4.2.0_1786886802758_0.5163313046442266"},"_hasShrinkwrap":false}},"time":{"created":"2025-03-12T15:50:46.662Z","modified":"2026-08-16T13:26:43.188Z","3.2.0":"2025-03-12T15:50:46.919Z","3.2.1":"2025-03-20T21:42:48.714Z","3.2.2":"2025-03-20T21:45:11.865Z","3.2.3":"2025-04-09T19:36:07.524Z","3.2.4":"2026-02-27T10:59:22.422Z","3.2.5":"2026-02-27T11:01:28.111Z","3.2.6":"2026-03-19T10:02:36.900Z","3.2.7":"2026-03-22T15:10:04.759Z","3.2.8":"2026-03-22T16:24:50.902Z","4.0.0":"2026-08-15T19:22:15.609Z","4.1.0":"2026-08-15T20:00:24.666Z","4.2.0":"2026-08-16T13:26:42.888Z"},"bugs":{"url":"https://github.com/ahmetilhn/handy-utils/issues"},"author":{"name":"Ahmet ilhan","email":"ahmetilhan.dev@gmail.com"},"license":"MIT","homepage":"https://github.com/ahmetilhn/handy-utils#readme","keywords":["javascript","utility","library","helpers","tools","functions","development","lodash","utils","isClient","isServer","isNumber","isDefined","isUndefined","isFunction","isArray","isObject","isDate","sleep","debounce"],"repository":{"type":"git","url":"git+https://github.com/ahmetilhn/handy-utils.git"},"description":"Handy utils offers developers a powerful and easy-to-use toolset. With its clean, modular and scalable code structure, it accelerates development processes and reduces code complexity. With a wide range of functions, it offers ready-made methods for solvi","maintainers":[{"name":"ahmetilhn","email":"ahmetilhan.dev@gmail.com"}],"readme":"# handy-utils\n\n<p align=\"center\">\n  <a href=\"https://www.npmjs.com/package/@ahmetilhn/handy-utils\"><img alt=\"npm version\" src=\"https://img.shields.io/npm/v/@ahmetilhn/handy-utils.svg\" /></a>\n  <a href=\"https://www.npmjs.com/package/@ahmetilhn/handy-utils\"><img alt=\"npm downloads\" src=\"https://img.shields.io/npm/dy/@ahmetilhn/handy-utils\" /></a>\n  <a href=\"./LICENSE\"><img alt=\"MIT License\" src=\"https://img.shields.io/badge/license-MIT-blue\" /></a>\n</p>\n\nA small, dependency-free toolkit of the checks and helpers most projects end up\nrewriting: type guards that actually narrow, a structural equality function that\ndoes not lie about `Map`s and `URL`s, a clone that survives circular references,\nand a handful of everyday utilities.\n\n- **22 functions, zero dependencies.**\n- **Written in TypeScript.** Every guard is a real type predicate.\n- **Correct on hard inputs.** Circular references, `NaN`, `-0`, `Map`, `Set`,\n  `Date`, `RegExp`, typed arrays and class instances are all handled.\n- **Isomorphic.** Nothing touches `window` unless you call a browser helper.\n- **100% test coverage**, enforced in CI across 267 tests.\n\n---\n\n## Table of contents\n\n- [Requirements](#requirements)\n- [Installation](#installation)\n- [Quick start](#quick-start)\n- [API reference](#api-reference)\n  - [Comparison and cloning](#comparison-and-cloning) — `isDeepEqual`, `deepClone`\n  - [Type guards](#type-guards) — `isArray`, `isBoolean`, `isDate`, `isDefined`, `isFunction`, `isNull`, `isNumber`, `isObject`, `isPlainObject`, `isUndefined`, `hasPlainObjectRecord`\n  - [Environment](#environment) — `isClient`, `isServer`\n  - [Device](#device) — `isAndroid`, `isIos`\n  - [Utilities](#utilities) — `debounce`, `normalize`, `sleep`, `watcher`, `withRetry`\n- [How values are compared](#how-values-are-compared)\n- [TypeScript](#typescript)\n- [Breaking changes in 4.0.0](#breaking-changes-in-400)\n- [Contributing](#contributing)\n- [Releasing](#releasing)\n- [License](#license)\n\n---\n\n## Requirements\n\nNode.js **24 or newer**. In the browser, any engine supporting ES2022 — every\nevergreen release since 2022.\n\n## Installation\n\n```bash\nnpm install @ahmetilhn/handy-utils\n```\n\n```bash\nyarn add @ahmetilhn/handy-utils\n```\n\n## Quick start\n\nEverything is a named export, so bundlers drop what you do not use.\n\n```ts\nimport { isDeepEqual, deepClone, isDefined, sleep } from \"@ahmetilhn/handy-utils\";\n\nisDeepEqual({ a: [1, 2] }, { a: [1, 2] }); // true\ndeepClone(new Map([[\"k\", { n: 1 }]])); // a real Map, deeply copied\n\nconst values = [1, null, 2];\nconst defined = values.filter(isDefined); // number[] — narrowed\n\nawait sleep(200);\n```\n\nCommonJS works the same way:\n\n```js\nconst { isDeepEqual } = require(\"@ahmetilhn/handy-utils\");\n```\n\n---\n\n## API reference\n\n### Comparison and cloning\n\n#### `isDeepEqual`\n\nStructural equality with `Object.is` semantics for primitives. Safe against\ncircular references.\n\n```ts\nisDeepEqual(valOne: unknown, valTwo: unknown): boolean\n```\n\n```ts\n// Primitives use Object.is: NaN matches itself, 0 and -0 stay distinct\nisDeepEqual(10, 10); // true\nisDeepEqual(NaN, NaN); // true\nisDeepEqual(0, -0); // false\nisDeepEqual(1, \"1\"); // false\nisDeepEqual(null, undefined); // false\n\n// Objects and arrays compare by content; key order is irrelevant\nisDeepEqual({ name: \"john\" }, { name: \"john\" }); // true\nisDeepEqual({ a: 1, b: 2 }, { b: 2, a: 1 }); // true\nisDeepEqual({ a: 1 }, { a: 1, b: 2 }); // false\nisDeepEqual([1, [2]], [1, [2]]); // true\nisDeepEqual([1, 2], [2, 1]); // false — order matters in arrays\n\n// Built-ins compare by value\nisDeepEqual(new Date(5), new Date(5)); // true\nisDeepEqual(/foo/g, /foo/i); // false — flags differ\nisDeepEqual(new Map([[\"a\", 1]]), new Map([[\"a\", 1]])); // true\nisDeepEqual(new Set([1, 2]), new Set([2, 1])); // true — order irrelevant\nisDeepEqual(new Error(\"boom\"), new Error(\"boom\")); // true\nisDeepEqual(new URL(\"https://a.com\"), new URL(\"https://b.com\")); // false\nisDeepEqual(new Uint8Array([1, 2]), new Uint8Array([1, 2])); // true\n\n// Class instances need a matching prototype as well as matching fields\nclass Point {\n  constructor(public x: number) {}\n}\nisDeepEqual(new Point(1), new Point(1)); // true\nisDeepEqual(new Point(1), { x: 1 }); // false\n\n// Functions are equal only by reference\nconst fn = () => 10;\nisDeepEqual(fn, fn); // true\nisDeepEqual(() => 10, () => 10); // false\n\n// Circular input is handled\nconst a: any = { name: \"a\" };\na.self = a;\nconst b: any = { name: \"a\" };\nb.self = b;\nisDeepEqual(a, b); // true\n```\n\n**Why functions compare by reference.** Two functions with identical source can\ncapture different closure state, so they are not interchangeable:\n\n```ts\nconst make = (k: number) => (x: number) => x + k;\nmake(1)(0); // 1\nmake(99)(0); // 99\n// Identical source, different behaviour — comparing toString() would call\n// these equal. lodash and Node's assert.deepStrictEqual agree: not equal.\n```\n\n**Values with no observable structure** — `WeakMap`, `WeakSet`, `Promise`,\ngenerators, DOM nodes — can only be compared by reference. See\n[How values are compared](#how-values-are-compared) for the full table.\n\n---\n\n#### `deepClone`\n\nRecursive structural clone that disconnects every data binding. Safe against\ncircular and shared references, and preserves prototypes.\n\n```ts\ndeepClone<T>(val: T): T\n```\n\n```ts\nconst original = { name: \"test\", tags: [\"a\"] };\nconst copy = deepClone(original);\n\ncopy !== original; // a new reference\ncopy.tags !== original.tags; // nested values copied too\nisDeepEqual(copy, original); // structurally identical\n\n// Built-ins keep their type\ndeepClone(new Map([[\"a\", 1]])) instanceof Map; // true\ndeepClone(new Set([1])) instanceof Set; // true\ndeepClone(/foo/g) instanceof RegExp; // true — source, flags and lastIndex kept\ndeepClone(new Date()) instanceof Date; // true\ndeepClone(new Uint8Array([1, 2])) instanceof Uint8Array; // true\ndeepClone(new Error(\"boom\")) instanceof Error; // true — name, stack, cause kept\n\n// Class instances keep their prototype and therefore their methods\nclass Person {\n  constructor(public name: string) {}\n  greet() {\n    return `hi ${this.name}`;\n  }\n}\ndeepClone(new Person(\"Ada\")).greet(); // \"hi Ada\"\n\n// Object.create(null) stays prototype-less\nObject.getPrototypeOf(deepClone(Object.create(null))); // null\n\n// Sparse arrays keep their length and their holes\ndeepClone([, 1, , 2]).length; // 4\n\n// Enumerable symbol keys are copied\nconst id = Symbol(\"id\");\ndeepClone({ [id]: 1 })[id]; // 1\n```\n\n**Circular and shared references:**\n\n```ts\nconst node: any = { name: \"root\" };\nnode.self = node;\nconst copy = deepClone(node);\ncopy.self === copy; // true — the cycle is rebuilt, not followed forever\n\n// Sharing is preserved within one clone\nconst shared = { n: 1 };\nconst cloned = deepClone({ a: shared, b: shared });\ncloned.a === cloned.b; // true\n```\n\n**Shared by reference on purpose:** functions, `WeakMap`, `WeakSet` and\n`Promise`. Copying them is not meaningful — a cloned function would break\nidentity comparisons, and a weak collection's contents are not enumerable.\n\n---\n\n### Type guards\n\nEvery guard below is a TypeScript type predicate, so it narrows inside `if`\nblocks and works as a `.filter()` argument.\n\n#### `isDefined`\n\nNeither `null` nor `undefined`. Narrows to `NonNullable<T>`.\n\n```ts\nisDefined<T>(val: T): val is NonNullable<T>\n```\n\n```ts\nisDefined(\"value\"); // true\nisDefined(0); // true — falsy, but defined\nisDefined(\"\"); // true\nisDefined(false); // true\nisDefined(null); // false\nisDefined(undefined); // false\n\n// Narrowing\ndeclare const maybe: string | null | undefined;\nif (isDefined(maybe)) {\n  maybe.toUpperCase(); // string\n}\n\n// As a filter predicate\nconst values: Array<number | null> = [1, null, 2];\nconst defined: number[] = values.filter(isDefined); // [1, 2]\n```\n\n#### `isNull`\n\n```ts\nisNull(val: unknown): val is null\n```\n\n```ts\nisNull(null); // true\nisNull(undefined); // false\nisNull(0); // false\n```\n\n#### `isUndefined`\n\n```ts\nisUndefined(val: unknown): val is undefined\n```\n\n```ts\nisUndefined(undefined); // true\nisUndefined(null); // false\n```\n\n#### `isNumber`\n\nA **finite** number. `NaN` and `Infinity` are numbers by `typeof` but not values\nyou can compute with, so both are rejected.\n\n```ts\nisNumber(val: unknown): val is number\n```\n\n```ts\nisNumber(1); // true\nisNumber(-5.5); // true\nisNumber(0); // true\n\nisNumber(NaN); // false\nisNumber(Infinity); // false\nisNumber(\"1\"); // false\nisNumber(null); // false\nisNumber(new Number(1)); // false — boxed, typeof is \"object\"\n```\n\n#### `isBoolean`\n\n```ts\nisBoolean(val: unknown): val is boolean\n```\n\n```ts\nisBoolean(true); // true\nisBoolean(false); // true\nisBoolean(0); // false\nisBoolean(new Boolean(true)); // false — boxed, not a primitive\n```\n\n#### `isArray`\n\n```ts\nisArray(val: unknown): val is Array<any>\n```\n\n```ts\nisArray([]); // true\nisArray([1, 2]); // true\nisArray(\"ab\"); // false\nisArray(new Uint8Array()); // false — a typed array is not an Array\nisArray({ length: 0 }); // false\n```\n\n#### `isObject`\n\nAny non-null value of `typeof \"object\"`. Arrays and dates count; functions do\nnot. For \"is this a `{}`-style object\", use [`isPlainObject`](#isplainobject).\n\n```ts\nisObject(val: unknown): val is object\n```\n\n```ts\nisObject({}); // true\nisObject([]); // true\nisObject(new Date()); // true\nisObject(null); // false\nisObject(() => {}); // false\n```\n\n#### `isPlainObject`\n\nAn object created by an object literal, `new Object()` or `Object.create(null)`\n— its prototype is `Object.prototype` or `null`. Built-ins and class instances\nare objects but not plain: their state lives in internal slots or behind\nprototype accessors, not in own enumerable keys.\n\n```ts\nisPlainObject(val: unknown): val is Record<string, unknown>\n```\n\n```ts\nisPlainObject({}); // true\nisPlainObject({ name: \"john\" }); // true\nisPlainObject(Object.create(null)); // true\n\nisPlainObject([]); // false\nisPlainObject(new Date()); // false\nisPlainObject(new Map()); // false\nisPlainObject(new URL(\"https://a.com\")); // false\nisPlainObject(new (class Point {})()); // false\nisPlainObject(null); // false\n```\n\nCross-realm objects (from an iframe or a `vm` context) are recognised too.\n\n#### `isDate`\n\nA `Date` instance. Note this is a type check, not a validity check — an invalid\ndate is still a `Date`.\n\n```ts\nisDate(val: unknown): val is Date\n```\n\n```ts\nisDate(new Date()); // true\nisDate(new Date(\"nope\")); // true — still a Date object\nisDate(Date.now()); // false — that is a number\nisDate(\"2023-01-01\"); // false\n```\n\nTo also require validity: `isDate(v) && !Number.isNaN(v.getTime())`.\n\n#### `isFunction`\n\nAny callable, including arrow, async, generator and async-generator functions,\nand class constructors.\n\n```ts\nisFunction(val: unknown): val is Function\n```\n\n```ts\nisFunction(() => {}); // true\nisFunction(async () => {}); // true\nisFunction(function* () {}); // true\nisFunction(class Foo {}); // true\nisFunction(Math.max); // true\n\nisFunction({}); // false\nisFunction(null); // false\n```\n\n#### `hasPlainObjectRecord`\n\nWhether a plain object carries any own enumerable entry. **Throws** for anything\nthat is not a plain object.\n\n```ts\nhasPlainObjectRecord(val: unknown): boolean\n```\n\n```ts\nhasPlainObjectRecord({}); // false\nhasPlainObjectRecord({ a: 1 }); // true\nhasPlainObjectRecord({ [Symbol(\"id\")]: 1 }); // true — symbol keys count\n\nhasPlainObjectRecord(new Map()); // throws Error\nhasPlainObjectRecord(null); // throws Error\n```\n\n---\n\n### Environment\n\n#### `isClient` / `isServer`\n\nWhether a `window` global exists. Safe to call anywhere — neither touches\n`window` unless it is present, so both are SSR-safe.\n\n```ts\nisClient(): boolean\nisServer(): boolean\n```\n\n```ts\n// In a browser\nisClient(); // true\nisServer(); // false\n\n// In Node\nisClient(); // false\nisServer(); // true\n```\n\n```ts\nif (isClient()) {\n  localStorage.setItem(\"visited\", \"1\");\n}\n```\n\n---\n\n### Device\n\nBoth detectors accept an optional user agent. Pass one to test a string\ndirectly — useful on the server, where request headers are the only source.\nOmit it to read `navigator.userAgent`, which **throws** outside the browser.\n\n#### `isAndroid`\n\n```ts\nisAndroid(userAgent?: string): boolean\n```\n\n```ts\nisAndroid(\"Mozilla/5.0 (Linux; Android 14; Pixel 8)\"); // true\nisAndroid(\"Mozilla/5.0 (iPhone; CPU iPhone OS 17_0)\"); // false\nisAndroid(\"\"); // false — an empty string is still a supplied user agent\n\nisAndroid(); // reads navigator; throws on the server\n```\n\n#### `isIos`\n\nAlso recognises **iPadOS 13 and newer**, which reports itself as \"Macintosh\" and\nis otherwise indistinguishable from a desktop Mac. The tell is touch support, so\nthis only applies when reading the live navigator; a supplied string is judged\non its own.\n\n```ts\nisIos(userAgent?: string): boolean\n```\n\n```ts\nisIos(\"Mozilla/5.0 (iPhone; CPU iPhone OS 17_0)\"); // true\nisIos(\"Mozilla/5.0 (iPad; CPU OS 17_0)\"); // true\nisIos(\"Mozilla/5.0 (Linux; Android 14)\"); // false\nisIos(\"\"); // false\n\nisIos(); // reads navigator, including the iPadOS check\n```\n\nServer-side usage from a request header:\n\n```ts\nconst ua = request.headers[\"user-agent\"] ?? \"\";\nconst isMobile = isIos(ua) || isAndroid(ua);\n```\n\n---\n\n### Utilities\n\n#### `debounce`\n\nDelay a function until the calls stop coming. Every call inside the window\nrestarts it, so a burst collapses into a single invocation with the arguments of\nthe last call. The receiver and the arguments are passed through untouched.\n\n```ts\ndebounce<T extends (...args: any[]) => any>(\n  fn: T,\n  wait: number,\n  options?: {\n    leading?: boolean;   // invoke on the leading edge — default false\n    trailing?: boolean;  // invoke on the trailing edge — default true\n    maxWait?: number;    // never defer longer than this\n  }\n): Debounced<T>;\n\ntype Debounced<T> = {\n  (...args: Parameters<T>): ReturnType<T> | undefined;\n  cancel: () => void;                      // drop the pending call\n  flush: () => ReturnType<T> | undefined;  // run the pending call now\n  pending: () => boolean;\n};\n```\n\n```ts\nconst search = debounce((term: string) => fetchResults(term), 300);\n\nsearch(\"h\");\nsearch(\"ha\");\nsearch(\"han\"); // only this one runs, 300ms after the last keystroke\n```\n\n```ts\n// Leading edge: react to the first event, ignore the rest of the burst\nconst onScrollStart = debounce(track, 200, { leading: true, trailing: false });\n\n// maxWait: a stream of events that never pauses still gets served every 500ms\nconst onResize = debounce(relayout, 100, { maxWait: 500 });\n\nonResize.pending(); // true while a call is waiting\nonResize.flush(); // run it now instead of waiting\nonResize.cancel(); // or throw it away — e.g. on unmount\n```\n\nNotes:\n\n- The return value is the result of the **previous** invocation; a deferred call\n  has not produced one yet. For a result you can await, wrap the call in a\n  promise yourself.\n- `cancel` resets the window completely, so the next call counts as the first.\n- `flush` returns the last result untouched when nothing is pending.\n- With `leading: true`, a lone call fires once — there is nothing left to\n  replay on the trailing edge.\n- `maxWait` shorter than `wait` is clamped to `wait`; a non-positive `wait`\n  defers to the next timer tick.\n- Timing reads `Date.now()`, so a clock jump starts a fresh window instead of\n  wedging the timer.\n\n#### `normalize`\n\nExpress a value as a percentage of a maximum. Capped at 100 and rounded to two\ndecimals.\n\n```ts\nnormalize(value: number, max: number): number\n```\n\n```ts\nnormalize(50, 200); // 25\nnormalize(1, 3); // 33.33\nnormalize(2, 3); // 66.67\nnormalize(100, 100); // 100\nnormalize(150, 100); // 100 — capped\nnormalize(0, 100); // 0\n```\n\nThrows on invalid input:\n\n```ts\nnormalize(5, 0); // RangeError — max must be greater than 0\nnormalize(5, -10); // RangeError\nnormalize(-1, 100); // RangeError — value must be non-negative\nnormalize(NaN, 100); // Error — Max or value must be number\nnormalize(null as any, 100); // Error — non-numbers are rejected\n```\n\n#### `sleep`\n\nPause for a number of milliseconds.\n\n```ts\nsleep(time: number): Promise<void>\n```\n\n```ts\nconst pollEverySecond = async () => {\n  while (running) {\n    await check();\n    await sleep(1000);\n  }\n};\n```\n\n#### `watcher`\n\nWrap an object in a proxy that reports every change. Assignments and deletions\nboth notify; a redundant assignment does not. The proxy writes through, so the\noriginal object stays in sync.\n\n```ts\nwatcher<T extends object>(target: T, onChange: WatcherCallback<T>): T\n\ntype WatcherCallback<T extends object> = (\n  key: keyof T,\n  value: T[keyof T] | undefined,      // undefined when the key was deleted\n  previous: T[keyof T] | undefined\n) => void;\n```\n\n```ts\nconst state = watcher({ count: 0, name: \"Ada\" }, (key, value, previous) => {\n  console.log(`${String(key)}: ${previous} → ${value}`);\n});\n\nstate.count = 1; // \"count: 0 → 1\"\nstate.count = 1; // nothing — the value did not change\nstate.name = \"Grace\"; // \"name: Ada → Grace\"\ndelete state.count; // \"count: 1 → undefined\"\n```\n\nNotes:\n\n- Equality uses `Object.is`, so assigning `NaN` over `NaN` is not a change.\n- Assigning to a key that does not exist yet always reports, even if the value\n  is `undefined`.\n- Setters on the target still run; the watcher does not bypass them.\n- Arrays work, but a single `push` reports both the new index and `length`.\n\n#### `withRetry`\n\nRun an async function, retrying with exponential backoff while it fails with a\ngiven error type. Returns `null` once the attempts are exhausted. Any other\nerror class is rethrown immediately, without retrying.\n\n```ts\nwithRetry<T>(props: {\n  fn: () => Promise<T>;\n  retries: number;   // total attempts, not extra attempts\n  delay: number;     // base delay in ms; waits delay * 2 ** attemptIndex\n  exception: new (...args: any[]) => Error;\n}): Promise<T | null>\n```\n\n```ts\nclass NetworkError extends Error {}\n\nconst data = await withRetry({\n  fn: () => fetchFromApi(),\n  retries: 3,\n  delay: 100, // waits 100ms after the first failure, then 200ms\n  exception: NetworkError,\n});\n\nif (data === null) {\n  // all three attempts threw a NetworkError\n}\n```\n\n```ts\n// A different error class is not retried\nawait withRetry({\n  fn: async () => {\n    throw new TypeError(\"bad input\");\n  },\n  retries: 3,\n  delay: 100,\n  exception: NetworkError,\n}); // rejects with the TypeError immediately\n```\n\nPass `exception: Error` to retry every error. Note that `retries: 0` returns\n`null` without ever calling `fn`.\n\n---\n\n## How values are compared\n\n`isDeepEqual` and `deepClone` classify values the same way. Knowing the table\nexplains both.\n\n| Input                                       | `isDeepEqual`                        | `deepClone`                     |\n| ------------------------------------------- | ------------------------------------ | ------------------------------- |\n| Primitives                                   | `Object.is` — `NaN` matches, `0 ≠ -0` | Returned as-is                  |\n| Plain objects, arrays                        | By content, key order irrelevant     | Deep copy, holes preserved      |\n| `Date`                                       | By timestamp                         | New `Date`                      |\n| `RegExp`                                     | By source and flags                  | New `RegExp`, `lastIndex` kept  |\n| `Map`, `Set`                                 | By content, insertion order irrelevant | New `Map` / `Set`, entries cloned |\n| `Error`                                      | By name and message                  | Same class, stack and cause kept |\n| Boxed `Number` / `String` / `Boolean`        | By primitive value                   | Deep copy                       |\n| `ArrayBuffer`, `DataView`, typed arrays      | Byte by byte                         | New buffer, bytes copied        |\n| `URL`, `URLSearchParams`                     | By string form                       | Deep copy                       |\n| Class instances                              | Same prototype **and** same fields   | Same prototype, fields copied   |\n| Functions                                    | Reference only                       | Shared by reference             |\n| `WeakMap`, `WeakSet`, `Promise`, generators, DOM nodes | Reference only            | Shared by reference             |\n| Circular references                          | Handled                              | Handled                         |\n\nOnly **own enumerable** properties participate, including symbol keys.\nInherited properties are ignored.\n\n---\n\n## TypeScript\n\nTypes ship with the package; nothing extra to install.\n\n```ts\nimport {\n  isDefined,\n  watcher,\n  type WatcherCallback,\n} from \"@ahmetilhn/handy-utils\";\n```\n\nEvery `is*` function is a type predicate, so narrowing works in conditionals,\nternaries and `.filter()`:\n\n```ts\ndeclare const input: unknown;\n\nif (isPlainObject(input)) {\n  input.anyKey; // Record<string, unknown>\n}\n\nconst cleaned = [\"a\", null, \"b\"].filter(isDefined); // string[]\n```\n\n`deepClone` preserves the input type:\n\n```ts\nconst copy = deepClone({ list: [1, 2], when: new Date() });\n// { list: number[]; when: Date }\n```\n\n---\n\n## Breaking changes in 4.0.0\n\n`isDeepEqual`, `deepClone`, `isPlainObject` and `isFunction` returned wrong\nanswers for several common inputs. Fixing them changes observable behaviour.\n\n| Case                                                          | 3.x                              | 4.0.0                                                       |\n| -------------------------------------------------------------- | -------------------------------- | ----------------------------------------------------------- |\n| `isPlainObject(new Map())`, `new URL()`, class instances        | `true`                           | `false` — only literals and `Object.create(null)` are plain  |\n| `isDeepEqual` on two `Map`/`Set`/`RegExp`/`Error`/`URL` values   | `true` regardless of content     | Compared by value                                            |\n| `isDeepEqual(NaN, NaN)`                                         | `false`                          | `true`                                                       |\n| `isDeepEqual(0, -0)`                                            | `true`                           | `false`                                                      |\n| `isDeepEqual` on two functions with equal source                | `true` (compared `toString()`)   | `false` — only the same reference is equal                   |\n| `isDeepEqual` on circular input                                 | `RangeError`                     | Handled                                                      |\n| `deepClone(new Map())`, `RegExp`, class instance                | `{}` — type and prototype lost   | Correct type and prototype                                   |\n| `deepClone` on circular input                                   | `RangeError`                     | Handled                                                      |\n| `deepClone([, 1, , 2])`                                         | `[1, 2]` — holes compacted       | Length and holes preserved                                   |\n| `isFunction(async () => {})`, generators                        | `false`                          | `true`                                                       |\n| `watcher`: assigning `undefined` to a new key                   | Key silently never created       | Key created, change reported                                 |\n| `watcher`: `delete proxy.key`                                   | Deleted without notifying        | Reported as a change with `undefined`                        |\n| `isNumber(Infinity)`                                            | `true`, while `NaN` was `false`  | `false` — finite numbers only                                |\n| `normalize(null, max)`                                          | `null` coerced to 0 and accepted | Throws, as the message always promised                       |\n| `isAndroid(\"\")` / `isIos(\"\")`                                   | Threw \"only works on client\"     | `false` — an empty UA is still a UA                          |\n| `hasPlainObjectRecord({ [Symbol()]: 1 })`                       | `false` — symbols invisible      | `true`                                                       |\n| `isDefined` narrowing                                           | Narrowed nothing                 | Narrows to `NonNullable<T>`                                  |\n| `sleep(ms)` return type                                         | `Promise<unknown>`               | `Promise<void>`                                              |\n\n`isDeepEqual` and `deepClone` also gained support for typed arrays,\n`ArrayBuffer`, `DataView`, boxed primitives and enumerable symbol keys.\n`isIos()` gained iPadOS 13+ detection. The minimum Node.js version is now 24.\n\nThe function comparison is the change most likely to affect callers: two\nfunctions with identical source can capture different closure state, so they are\nnot interchangeable. Compare functions by reference, as lodash and Node's\n`assert.deepStrictEqual` do.\n\n---\n\n## Contributing\n\n```bash\nnpm install\nnpm run verify   # typecheck, test with coverage, build\n```\n\n267 tests, enforced at 100% for statements, branches, functions and lines.\n\n## Releasing\n\nReleases are automatic. Bump `version` in `package.json` and merge to `master`:\n\n```bash\nnpm version patch   # or minor / major\ngit push origin master\n```\n\nThe publish workflow then type-checks, tests, builds, smoke-tests the packed\ntarball in a clean project, publishes to npm with provenance, and pushes a\n`v<version>` tag.\n\nA commit that does not change the version is not an error — the workflow sees\nthe version already on npm and skips the release.\n\n## License\n\nMIT © [Ahmet ilhan](https://github.com/ahmetilhn)\n","readmeFilename":"README.md"}