diff --git a/bench/isIterable.bench.ts b/bench/isIterable.bench.ts new file mode 100644 index 0000000..bcac6c5 --- /dev/null +++ b/bench/isIterable.bench.ts @@ -0,0 +1,9 @@ +import { bench } from 'vitest'; + +import { isIterable } from '../src/predicate/isIterable.js'; + +const data: unknown[] = []; + +bench('isIterable()', () => { + isIterable(data); +}); diff --git a/bench/isPromiseLike.bench.ts b/bench/isPromiseLike.bench.ts new file mode 100644 index 0000000..6714fc1 --- /dev/null +++ b/bench/isPromiseLike.bench.ts @@ -0,0 +1,28 @@ +import { bench } from 'vitest'; + +import { isFunction } from '../src/predicate/isFunction.js'; +import { isObjectWith } from '../src/predicate/isObjectWith.js'; +import { isPromiseLike } from '../src/predicate/isPromiseLike.js'; + +function isPromiseLike2(input: unknown): input is PromiseLike { + return isObjectWith(input, 'then') && isFunction(input.then) && input.then.length === 2; +} + +const resolvedPromise = Promise.resolve(); + +const promiseLikeObject = { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + then(_onfulfilled: unknown, _onrejected: unknown): void { + // noop + }, +}; + +bench('isPromiseLike()', () => { + isPromiseLike(resolvedPromise); + isPromiseLike(promiseLikeObject); +}); + +bench('isPromiseLike2()', () => { + isPromiseLike2(resolvedPromise); + isPromiseLike2(promiseLikeObject); +}); diff --git a/package.json b/package.json index 8e14c69..8ce387d 100644 --- a/package.json +++ b/package.json @@ -28,10 +28,10 @@ "require": "./dist/cjs/index.js", "import": "./dist/mjs/index.js" }, - "./predicate-factory": { - "types": "./dist/types/predicate-factory/index.d.ts", - "require": "./dist/cjs/predicate-factory/index.js", - "import": "./dist/mjs/predicate-factory/index.js" + "./predicate/factory": { + "types": "./dist/types/predicate/factory/index.d.ts", + "require": "./dist/cjs/predicate/factory/index.js", + "import": "./dist/mjs/predicate/factory/index.js" }, "./predicate": { "types": "./dist/types/predicate/index.d.ts", @@ -72,7 +72,7 @@ "prebuild": "pnpm clean", "build": "tsc --build tsconfig.cjs.json tsconfig.mjs.json --force", "postbuild": "echo '{\"type\":\"commonjs\"}' > ./dist/cjs/package.json && echo '{\"type\":\"module\"}' > ./dist/mjs/package.json && pnpm validate", - "typecheck": "tsc --build --verbose --noEmit", + "typecheck": "tsc --build tsconfig.mjs.json tsconfig.test.json --verbose --noEmit", "lint": "eslint ./*.{js,cjs,mjs,ts,cts,mts} ./src/ ./bench/ ./test/ --ext .ts,.mjs,.cjs", "bench": "vitest bench", "test": "vitest --typecheck", @@ -97,7 +97,7 @@ "cspell": "^8.17.5", "eslint": "^8.57.1", "eslint-config-prettier": "^10.1.1", - "eslint-import-resolver-typescript": "^3.8.7", + "eslint-import-resolver-typescript": "^3.9.1", "eslint-plugin-import": "^2.31.0", "husky": "^9.1.7", "jsdom": "^26.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d599ee6..82251c1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -45,11 +45,11 @@ importers: specifier: ^10.1.1 version: 10.1.1(eslint@8.57.1) eslint-import-resolver-typescript: - specifier: ^3.8.7 - version: 3.8.7(eslint-plugin-import@2.31.0)(eslint@8.57.1) + specifier: ^3.9.1 + version: 3.9.1(eslint-plugin-import@2.31.0)(eslint@8.57.1) eslint-plugin-import: specifier: ^2.31.0 - version: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-typescript@3.8.7)(eslint@8.57.1) + version: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-typescript@3.9.1)(eslint@8.57.1) husky: specifier: ^9.1.7 version: 9.1.7 @@ -432,6 +432,15 @@ packages: resolution: {integrity: sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==} engines: {node: '>=18'} + '@emnapi/core@1.3.1': + resolution: {integrity: sha512-pVGjBIt1Y6gg3EJN8jTcfpP/+uuRksIo055oE/OBkDNcjZqVbfkWCksG1Jp4yZnj3iKWyWX8fdG/j6UDYPbFog==} + + '@emnapi/runtime@1.3.1': + resolution: {integrity: sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==} + + '@emnapi/wasi-threads@1.0.1': + resolution: {integrity: sha512-iIBu7mwkq4UQGeMEM8bLwNK962nXdhodeScX4slfQnRhEMMzvYivHhutCIk8uojvmASXXPC2WNEjwxFWk72Oqw==} + '@esbuild/aix-ppc64@0.25.1': resolution: {integrity: sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==} engines: {node: '>=18'} @@ -646,6 +655,9 @@ packages: '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + '@napi-rs/wasm-runtime@0.2.7': + resolution: {integrity: sha512-5yximcFK5FNompXfJFoWanu5l8v1hNGqNHh9du1xETp9HWk/B/PzvchX55WYOPaIeNglG8++68AAiauBAtbnzw==} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -957,6 +969,9 @@ packages: resolution: {integrity: sha512-UUYHISyhCU3ZgN8yaear3cGATHb3SMuKHsQ/nVbHXcmnBf+LzQ/cQfhNG+rfaSHgqGKNEm2cOCLVLELStUQ1JA==} engines: {node: ^18.17.0 || >=20.5.0} + '@tybys/wasm-util@0.9.0': + resolution: {integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==} + '@types/conventional-commits-parser@5.0.1': resolution: {integrity: sha512-7uz5EHdzz2TqoMfV7ee61Egf5y6NkcO4FB/1iCCQnbeiI1F3xzv3vK5dBCXUCLQgGYS+mUeigK1iKQzvED+QnQ==} @@ -1033,6 +1048,61 @@ packages: '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + '@unrs/rspack-resolver-binding-darwin-arm64@1.1.2': + resolution: {integrity: sha512-bQx2L40UF5XxsXwkD26PzuspqUbUswWVbmclmUC+c83Cv/EFrFJ1JaZj5Q5jyYglKGOtyIWY/hXTCdWRN9vT0Q==} + cpu: [arm64] + os: [darwin] + + '@unrs/rspack-resolver-binding-darwin-x64@1.1.2': + resolution: {integrity: sha512-dMi9a7//BsuPTnhWEDxmdKZ6wxQlPnAob8VSjefGbKX/a+pHfTaX1pm/jv2VPdarP96IIjCKPatJS/TtLQeGQA==} + cpu: [x64] + os: [darwin] + + '@unrs/rspack-resolver-binding-freebsd-x64@1.1.2': + resolution: {integrity: sha512-RiBZQ+LSORQObfhV1yH7jGz+4sN3SDYtV53jgc8tUVvqdqVDaUm1KA3zHLffmoiYNGrYkE3sSreGC+FVpsB4Vg==} + cpu: [x64] + os: [freebsd] + + '@unrs/rspack-resolver-binding-linux-arm-gnueabihf@1.1.2': + resolution: {integrity: sha512-IyKIFBtOvuPCJt1WPx9e9ovTGhZzrIbW11vWzw4aPmx3VShE+YcMpAldqQubdCep0UVKZyFt+2hQDQZwFiJ4jg==} + cpu: [arm] + os: [linux] + + '@unrs/rspack-resolver-binding-linux-arm64-gnu@1.1.2': + resolution: {integrity: sha512-RfYtlCtJrv5i6TO4dSlpbyOJX9Zbhmkqrr9hjDfr6YyE5KD0ywLRzw8UjXsohxG1XWgRpb2tvPuRYtURJwbqWg==} + cpu: [arm64] + os: [linux] + + '@unrs/rspack-resolver-binding-linux-arm64-musl@1.1.2': + resolution: {integrity: sha512-MaITzkoqsn1Rm3+YnplubgAQEfOt+2jHfFvuFhXseUfcfbxe8Zyc3TM7LKwgv7mRVjIl+/yYN5JqL0cjbnhAnQ==} + cpu: [arm64] + os: [linux] + + '@unrs/rspack-resolver-binding-linux-x64-gnu@1.1.2': + resolution: {integrity: sha512-Nu981XmzQqis/uB3j4Gi3p5BYCd/zReU5zbJmjMrEH7IIRH0dxZpdOmS/+KwEk6ao7Xd8P2D2gDHpHD/QTp0aQ==} + cpu: [x64] + os: [linux] + + '@unrs/rspack-resolver-binding-linux-x64-musl@1.1.2': + resolution: {integrity: sha512-xJupeDvaRpV0ADMuG1dY9jkOjhUzTqtykvchiU2NldSD+nafSUcMWnoqzNUx7HGiqbTMOw9d9xT8ZiFs+6ZFyQ==} + cpu: [x64] + os: [linux] + + '@unrs/rspack-resolver-binding-wasm32-wasi@1.1.2': + resolution: {integrity: sha512-un6X/xInks+KEgGpIHFV8BdoODHRohaDRvOwtjq+FXuoI4Ga0P6sLRvf4rPSZDvoMnqUhZtVNG0jG9oxOnrrLQ==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@unrs/rspack-resolver-binding-win32-arm64-msvc@1.1.2': + resolution: {integrity: sha512-2lCFkeT1HYUb/OOStBS1m67aZOf9BQxRA+Wf/xs94CGgzmoQt7H4V/BrkB/GSGKsudXjkiwt2oHNkHiowAS90A==} + cpu: [arm64] + os: [win32] + + '@unrs/rspack-resolver-binding-win32-x64-msvc@1.1.2': + resolution: {integrity: sha512-EYfya5HCQ/8Yfy7rvAAX2rGytu81+d/CIhNCbZfNKLQ690/qFsdEeTXRsMQW1afHoluMM50PsjPYu8ndy8fSQg==} + cpu: [x64] + os: [win32] + '@vitest/coverage-v8@3.0.8': resolution: {integrity: sha512-y7SAKsQirsEJ2F8bulBck4DoluhI2EEgTimHd6EEUgJBGKy9tC25cpywh1MH4FvDGoG2Unt7+asVd1kj4qOSAw==} peerDependencies: @@ -1174,8 +1244,8 @@ packages: resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} engines: {node: '>=8'} - array.prototype.findlastindex@1.2.5: - resolution: {integrity: sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==} + array.prototype.findlastindex@1.2.6: + resolution: {integrity: sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==} engines: {node: '>= 0.4'} array.prototype.flat@1.3.3: @@ -1593,10 +1663,6 @@ packages: encoding@0.1.13: resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} - enhanced-resolve@5.18.1: - resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==} - engines: {node: '>=10.13.0'} - entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} @@ -1684,8 +1750,8 @@ packages: eslint-import-resolver-node@0.3.9: resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} - eslint-import-resolver-typescript@3.8.7: - resolution: {integrity: sha512-U7k84gOzrfl09c33qrIbD3TkWTWu3nt3dK5sDajHSekfoLlYGusIwSdPlPzVeA6TFpi0Wpj+ZdBD8hX4hxPoww==} + eslint-import-resolver-typescript@3.9.1: + resolution: {integrity: sha512-euxa5rTGqHeqVxmOHT25hpk58PxkQ4mNoX6Yun4ooGaCHAxOCojJYNvjmyeOQxj/LyW+3fulH0+xtk+p2kPPTw==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: eslint: '*' @@ -2674,8 +2740,8 @@ packages: mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - nanoid@3.3.9: - resolution: {integrity: sha512-SppoicMGpZvbF1l3z4x7No3OlIjP7QJvC9XR7AhZr1kL133KHnKPztkKDc+Ir4aJ/1VhTySrtKhrsycmrMQfvg==} + nanoid@3.3.10: + resolution: {integrity: sha512-vSJJTG+t/dIKAUhUDw/dLdZ9s//5OxcHqLaDWWrW4Cdq7o6tdLIczUkMXt2MBNmk6sJRZBZRXVixs7URY1CmIg==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true @@ -3209,6 +3275,9 @@ packages: rrweb-cssom@0.8.0: resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==} + rspack-resolver@1.1.2: + resolution: {integrity: sha512-eHhz+9JWHFdbl/CVVqEP6kviLFZqw1s0MWxLdsGMtUKUspSO3SERptPohmrUIC9jT1bGV9Bd3+r8AmWbdfNAzQ==} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -3376,8 +3445,8 @@ packages: resolution: {integrity: sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ==} engines: {node: ^18.17.0 || >=20.5.0} - stable-hash@0.0.4: - resolution: {integrity: sha512-LjdcbuBeLcdETCrPn9i8AYAZ1eCtu4ECAWtP7UleOiZ9LzVxRzzUZEoZ8zB24nhkQnDWyET0I+3sWokSDS3E7g==} + stable-hash@0.0.5: + resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==} stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} @@ -3470,10 +3539,6 @@ packages: symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} - tapable@2.2.1: - resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} - engines: {node: '>=6'} - tar@6.2.1: resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} engines: {node: '>=10'} @@ -3555,8 +3620,8 @@ packages: resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==} engines: {node: '>=16'} - tr46@5.0.0: - resolution: {integrity: sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==} + tr46@5.1.0: + resolution: {integrity: sha512-IUWnUK7ADYR5Sl1fZlO1INDUhVhatWl7BtJWsIhwJ0UAK7ilzzIa8uIqOO/aYVWHZPJkKbEL+362wrzoeRF7bw==} engines: {node: '>=18'} traverse@0.6.8: @@ -3576,6 +3641,9 @@ packages: tsconfig-paths@3.15.0: resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tuf-js@3.0.1: resolution: {integrity: sha512-+68OP1ZzSF84rTckf3FA95vJ1Zlx/uaXyiiKyPd1pA4rZNkpEvDAKmsu1xUSmbF/chCRYgZ6UZkDwC7PmzmAyA==} engines: {node: ^18.17.0 || >=20.5.0} @@ -3691,8 +3759,8 @@ packages: engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true - vite@6.2.1: - resolution: {integrity: sha512-n2GnqDb6XPhlt9B8olZPrgMD/es/Nd1RdChF6CBD/fHW6pUyUTt2sQW2fPRX5GiD9XEa6+8A6A4f2vT6pSsE7Q==} + vite@6.2.2: + resolution: {integrity: sha512-yW7PeMM+LkDzc7CgJuRLMW2Jz0FxMOsVJ8Lv3gpgW9WLcb9cTW+121UEr1hvmfR7w3SegR5ItvYyzVz1vxNJgQ==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true peerDependencies: @@ -3784,8 +3852,8 @@ packages: resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} engines: {node: '>=18'} - whatwg-url@14.1.1: - resolution: {integrity: sha512-mDGf9diDad/giZ/Sm9Xi2YcyzaFpbdLpJPr+E9fSkyQ7KpQD4SdFcugkRQYzhmfI4KeV4Qpnn2sKPdo+kmsgRQ==} + whatwg-url@14.2.0: + resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==} engines: {node: '>=18'} which-boxed-primitive@1.1.1: @@ -4296,6 +4364,22 @@ snapshots: '@csstools/css-tokenizer@3.0.3': {} + '@emnapi/core@1.3.1': + dependencies: + '@emnapi/wasi-threads': 1.0.1 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.3.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.0.1': + dependencies: + tslib: 2.8.1 + optional: true + '@esbuild/aix-ppc64@0.25.1': optional: true @@ -4440,6 +4524,13 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 + '@napi-rs/wasm-runtime@0.2.7': + dependencies: + '@emnapi/core': 1.3.1 + '@emnapi/runtime': 1.3.1 + '@tybys/wasm-util': 0.9.0 + optional: true + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -4827,6 +4918,11 @@ snapshots: '@tufjs/canonical-json': 2.0.0 minimatch: 9.0.5 + '@tybys/wasm-util@0.9.0': + dependencies: + tslib: 2.8.1 + optional: true + '@types/conventional-commits-parser@5.0.1': dependencies: '@types/node': 22.13.10 @@ -4924,6 +5020,41 @@ snapshots: '@ungap/structured-clone@1.3.0': {} + '@unrs/rspack-resolver-binding-darwin-arm64@1.1.2': + optional: true + + '@unrs/rspack-resolver-binding-darwin-x64@1.1.2': + optional: true + + '@unrs/rspack-resolver-binding-freebsd-x64@1.1.2': + optional: true + + '@unrs/rspack-resolver-binding-linux-arm-gnueabihf@1.1.2': + optional: true + + '@unrs/rspack-resolver-binding-linux-arm64-gnu@1.1.2': + optional: true + + '@unrs/rspack-resolver-binding-linux-arm64-musl@1.1.2': + optional: true + + '@unrs/rspack-resolver-binding-linux-x64-gnu@1.1.2': + optional: true + + '@unrs/rspack-resolver-binding-linux-x64-musl@1.1.2': + optional: true + + '@unrs/rspack-resolver-binding-wasm32-wasi@1.1.2': + dependencies: + '@napi-rs/wasm-runtime': 0.2.7 + optional: true + + '@unrs/rspack-resolver-binding-win32-arm64-msvc@1.1.2': + optional: true + + '@unrs/rspack-resolver-binding-win32-x64-msvc@1.1.2': + optional: true + '@vitest/coverage-v8@3.0.8(vitest@3.0.8(@types/node@22.13.10)(jiti@2.4.2)(jsdom@26.0.0)(yaml@2.7.0))': dependencies: '@ampproject/remapping': 2.3.0 @@ -4949,13 +5080,13 @@ snapshots: chai: 5.2.0 tinyrainbow: 2.0.0 - '@vitest/mocker@3.0.8(vite@6.2.1(@types/node@22.13.10)(jiti@2.4.2)(yaml@2.7.0))': + '@vitest/mocker@3.0.8(vite@6.2.2(@types/node@22.13.10)(jiti@2.4.2)(yaml@2.7.0))': dependencies: '@vitest/spy': 3.0.8 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: - vite: 6.2.1(@types/node@22.13.10)(jiti@2.4.2)(yaml@2.7.0) + vite: 6.2.2(@types/node@22.13.10)(jiti@2.4.2)(yaml@2.7.0) '@vitest/pretty-format@3.0.8': dependencies: @@ -5080,9 +5211,10 @@ snapshots: array-union@2.1.0: {} - array.prototype.findlastindex@1.2.5: + array.prototype.findlastindex@1.2.6: dependencies: call-bind: 1.0.8 + call-bound: 1.0.4 define-properties: 1.2.1 es-abstract: 1.23.9 es-errors: 1.3.0 @@ -5489,7 +5621,7 @@ snapshots: data-urls@5.0.0: dependencies: whatwg-mimetype: 4.0.0 - whatwg-url: 14.1.1 + whatwg-url: 14.2.0 data-view-buffer@1.0.2: dependencies: @@ -5580,11 +5712,6 @@ snapshots: iconv-lite: 0.6.3 optional: true - enhanced-resolve@5.18.1: - dependencies: - graceful-fs: 4.2.11 - tapable: 2.2.1 - entities@4.5.0: {} env-ci@11.1.0: @@ -5733,44 +5860,44 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.8.7(eslint-plugin-import@2.31.0)(eslint@8.57.1): + eslint-import-resolver-typescript@3.9.1(eslint-plugin-import@2.31.0)(eslint@8.57.1): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.0 - enhanced-resolve: 5.18.1 eslint: 8.57.1 get-tsconfig: 4.10.0 is-bun-module: 1.3.0 - stable-hash: 0.0.4 + rspack-resolver: 1.1.2 + stable-hash: 0.0.5 tinyglobby: 0.2.12 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-typescript@3.8.7)(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-typescript@3.9.1)(eslint@8.57.1) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.8.7)(eslint@8.57.1): + eslint-module-utils@2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.9.1)(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.8.2) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.8.7(eslint-plugin-import@2.31.0)(eslint@8.57.1) + eslint-import-resolver-typescript: 3.9.1(eslint-plugin-import@2.31.0)(eslint@8.57.1) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-typescript@3.8.7)(eslint@8.57.1): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-typescript@3.9.1)(eslint@8.57.1): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 - array.prototype.findlastindex: 1.2.5 + array.prototype.findlastindex: 1.2.6 array.prototype.flat: 1.3.3 array.prototype.flatmap: 1.3.3 debug: 3.2.7 doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.8.7)(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.9.1)(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -6521,7 +6648,7 @@ snapshots: webidl-conversions: 7.0.0 whatwg-encoding: 3.1.1 whatwg-mimetype: 4.0.0 - whatwg-url: 14.1.1 + whatwg-url: 14.2.0 ws: 8.18.1 xml-name-validator: 5.0.0 transitivePeerDependencies: @@ -6798,7 +6925,7 @@ snapshots: object-assign: 4.1.1 thenify-all: 1.6.0 - nanoid@3.3.9: {} + nanoid@3.3.10: {} natural-compare@1.4.0: {} @@ -7148,7 +7275,7 @@ snapshots: postcss@8.5.3: dependencies: - nanoid: 3.3.9 + nanoid: 3.3.10 picocolors: 1.1.1 source-map-js: 1.2.1 @@ -7312,6 +7439,20 @@ snapshots: rrweb-cssom@0.8.0: {} + rspack-resolver@1.1.2: + optionalDependencies: + '@unrs/rspack-resolver-binding-darwin-arm64': 1.1.2 + '@unrs/rspack-resolver-binding-darwin-x64': 1.1.2 + '@unrs/rspack-resolver-binding-freebsd-x64': 1.1.2 + '@unrs/rspack-resolver-binding-linux-arm-gnueabihf': 1.1.2 + '@unrs/rspack-resolver-binding-linux-arm64-gnu': 1.1.2 + '@unrs/rspack-resolver-binding-linux-arm64-musl': 1.1.2 + '@unrs/rspack-resolver-binding-linux-x64-gnu': 1.1.2 + '@unrs/rspack-resolver-binding-linux-x64-musl': 1.1.2 + '@unrs/rspack-resolver-binding-wasm32-wasi': 1.1.2 + '@unrs/rspack-resolver-binding-win32-arm64-msvc': 1.1.2 + '@unrs/rspack-resolver-binding-win32-x64-msvc': 1.1.2 + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -7530,7 +7671,7 @@ snapshots: dependencies: minipass: 7.1.2 - stable-hash@0.0.4: {} + stable-hash@0.0.5: {} stackback@0.0.2: {} @@ -7628,8 +7769,6 @@ snapshots: symbol-tree@3.2.4: {} - tapable@2.2.1: {} - tar@6.2.1: dependencies: chownr: 2.0.0 @@ -7715,7 +7854,7 @@ snapshots: dependencies: tldts: 6.1.84 - tr46@5.0.0: + tr46@5.1.0: dependencies: punycode: 2.3.1 @@ -7734,6 +7873,9 @@ snapshots: minimist: 1.2.8 strip-bom: 3.0.0 + tslib@2.8.1: + optional: true + tuf-js@3.0.1: dependencies: '@tufjs/models': 3.0.1 @@ -7852,7 +7994,7 @@ snapshots: debug: 4.4.0 es-module-lexer: 1.6.0 pathe: 2.0.3 - vite: 6.2.1(@types/node@22.13.10)(jiti@2.4.2)(yaml@2.7.0) + vite: 6.2.2(@types/node@22.13.10)(jiti@2.4.2)(yaml@2.7.0) transitivePeerDependencies: - '@types/node' - jiti @@ -7867,7 +8009,7 @@ snapshots: - tsx - yaml - vite@6.2.1(@types/node@22.13.10)(jiti@2.4.2)(yaml@2.7.0): + vite@6.2.2(@types/node@22.13.10)(jiti@2.4.2)(yaml@2.7.0): dependencies: esbuild: 0.25.1 postcss: 8.5.3 @@ -7881,7 +8023,7 @@ snapshots: vitest@3.0.8(@types/node@22.13.10)(jiti@2.4.2)(jsdom@26.0.0)(yaml@2.7.0): dependencies: '@vitest/expect': 3.0.8 - '@vitest/mocker': 3.0.8(vite@6.2.1(@types/node@22.13.10)(jiti@2.4.2)(yaml@2.7.0)) + '@vitest/mocker': 3.0.8(vite@6.2.2(@types/node@22.13.10)(jiti@2.4.2)(yaml@2.7.0)) '@vitest/pretty-format': 3.0.8 '@vitest/runner': 3.0.8 '@vitest/snapshot': 3.0.8 @@ -7897,7 +8039,7 @@ snapshots: tinyexec: 0.3.2 tinypool: 1.0.2 tinyrainbow: 2.0.0 - vite: 6.2.1(@types/node@22.13.10)(jiti@2.4.2)(yaml@2.7.0) + vite: 6.2.2(@types/node@22.13.10)(jiti@2.4.2)(yaml@2.7.0) vite-node: 3.0.8(@types/node@22.13.10)(jiti@2.4.2)(yaml@2.7.0) why-is-node-running: 2.3.0 optionalDependencies: @@ -7935,9 +8077,9 @@ snapshots: whatwg-mimetype@4.0.0: {} - whatwg-url@14.1.1: + whatwg-url@14.2.0: dependencies: - tr46: 5.0.0 + tr46: 5.1.0 webidl-conversions: 7.0.0 which-boxed-primitive@1.1.1: diff --git a/src/assertion/assertIsAsyncIterable.ts b/src/assertion/assertIsAsyncIterable.ts index 916b988..6f324f0 100644 --- a/src/assertion/assertIsAsyncIterable.ts +++ b/src/assertion/assertIsAsyncIterable.ts @@ -2,11 +2,11 @@ import { isAsyncIterable } from '../predicate/isAsyncIterable.js'; import { getError } from './getError.js'; -export function assertIsAsyncIterable( +export function assertIsAsyncIterable( input: unknown, error: string | Error = 'input is not an AsyncIterable', -): asserts input is AsyncIterable { - if (!isAsyncIterable(input)) { +): asserts input is AsyncIterable { + if (!isAsyncIterable(input)) { throw getError(error); } } diff --git a/src/assertion/assertIsFunction.ts b/src/assertion/assertIsFunction.ts index b0140d4..315228a 100644 --- a/src/assertion/assertIsFunction.ts +++ b/src/assertion/assertIsFunction.ts @@ -1,14 +1,13 @@ +/* eslint-disable @typescript-eslint/ban-types */ import { isFunction } from '../predicate/isFunction.js'; import { getError } from './getError.js'; -import type { AnyFunction } from '../types/common.js'; - -export function assertIsFunction( +export function assertIsFunction( input: unknown, error: string | Error = 'input is not a function', -): asserts input is Func { - if (!isFunction(input)) { +): asserts input is Function { + if (!isFunction(input)) { throw getError(error); } } diff --git a/src/assertion/assertIsIterable.ts b/src/assertion/assertIsIterable.ts index a1a8146..f2a96ba 100644 --- a/src/assertion/assertIsIterable.ts +++ b/src/assertion/assertIsIterable.ts @@ -6,7 +6,7 @@ export function assertIsIterable( input: unknown, error: string | Error = 'input is not an Iterable', ): asserts input is Iterable { - if (!isIterable(input)) { + if (!isIterable(input)) { throw getError(error); } } diff --git a/src/assertion/assertIsStringMatching.ts b/src/assertion/assertIsStringMatching.ts index 10f3798..4d5e600 100644 --- a/src/assertion/assertIsStringMatching.ts +++ b/src/assertion/assertIsStringMatching.ts @@ -1,4 +1,4 @@ -import { createStringMatchingPredicate } from '../predicate-factory/createStringMatchingPredicate.js'; +import { matching } from '../predicate/factory/matching.js'; import { getError } from './getError.js'; @@ -7,7 +7,7 @@ export function assertIsStringMatching( pattern: RegExp, error: string | Error = 'input is not a string that matches the pattern', ): asserts input is string { - if (!createStringMatchingPredicate(pattern)(input)) { + if (!matching(pattern)(input)) { throw getError(error); } } diff --git a/src/index.ts b/src/index.ts index 4f290af..d22f1c5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,6 @@ // Folders export * from './assertion/index.js'; -export * from './predicate-factory/index.js'; +export * from './predicate/factory/index.js'; export * from './predicate/index.js'; export * from './types/index.js'; diff --git a/src/predicate-factory/createNumberRangePredicate.ts b/src/predicate-factory/createNumberRangePredicate.ts deleted file mode 100644 index 00fdf61..0000000 --- a/src/predicate-factory/createNumberRangePredicate.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { assertIsNumber } from '../assertion/assertIsNumber.js'; -import { isNumber } from '../predicate/isNumber.js'; - -import type { TypePredicateFn } from '../types/functions.js'; - -export const createNumberRangePredicate = ( - min: number, - max: number = Number.POSITIVE_INFINITY, -): TypePredicateFn => { - assertIsNumber(min, 'min is not a number'); - assertIsNumber(max, 'max is not a number'); - - if (min === Number.NEGATIVE_INFINITY && max === Number.POSITIVE_INFINITY) { - throw new RangeError('min and max cannot be -Infinity and Infinity'); - } - - if (min > max) { - throw new RangeError('min cannot be greater than max'); - } - - if (min === max) { - throw new RangeError('min and max cannot be the same'); - } - - return (input: unknown): input is Type => isNumber(input) && input >= min && input <= max; -}; diff --git a/src/predicate-factory/createStringLengthRangePredicate.ts b/src/predicate-factory/createStringLengthRangePredicate.ts deleted file mode 100644 index 701e4d8..0000000 --- a/src/predicate-factory/createStringLengthRangePredicate.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { TypePredicateFn } from '../types/functions.js'; - -export const createStringLengthRangePredicate = ( - min: number, - max: number = Number.POSITIVE_INFINITY, -): TypePredicateFn => { - if (min < 0) { - throw new RangeError('min must be >= 0'); - } - - return (input: unknown): input is Type => typeof input === 'string' && input.length >= min && input.length <= max; -}; diff --git a/src/predicate-factory/everyItem.ts b/src/predicate-factory/everyItem.ts deleted file mode 100644 index cfebce7..0000000 --- a/src/predicate-factory/everyItem.ts +++ /dev/null @@ -1,6 +0,0 @@ -import type { TypePredicateFn } from '../types/functions.js'; - -export const everyItem = - (predicate: TypePredicateFn): TypePredicateFn => - (input: unknown): input is T[] => - Array.isArray(input) && input.every(predicate); diff --git a/src/predicate-factory/index.ts b/src/predicate-factory/index.ts deleted file mode 100644 index 4e56957..0000000 --- a/src/predicate-factory/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -export * from './allOf.js'; -export * from './anyOf.js'; -export * from './createIsEnumPredicate.js'; -export * from './createIsInstanceOfPredicate.js'; -export * from './createIsTuplePredicate.js'; -export * from './createNumberRangePredicate.js'; -export * from './createObjectShapePredicate.js'; -export * from './createStringLengthRangePredicate.js'; -export * from './createStringMatchingPredicate.js'; -export * from './everyItem.js'; -export * from './is.js'; -export * from './maybeArray.js'; -export * from './maybeNull.js'; -export * from './maybeUndefined.js'; diff --git a/src/predicate-factory/allOf.ts b/src/predicate/factory/allOf.ts similarity index 85% rename from src/predicate-factory/allOf.ts rename to src/predicate/factory/allOf.ts index f27db7e..927d5d2 100644 --- a/src/predicate-factory/allOf.ts +++ b/src/predicate/factory/allOf.ts @@ -1,5 +1,5 @@ -import type { InferPredicatesReturnType, TypePredicateFn } from '../types/functions.js'; -import type { IntersectionOf } from '../types/utils.js'; +import type { InferPredicatesReturnType, TypePredicateFn } from '../../types/functions.js'; +import type { IntersectionOf } from '../../types/utils.js'; /** * All predicates must pass diff --git a/src/predicate-factory/anyOf.ts b/src/predicate/factory/anyOf.ts similarity index 86% rename from src/predicate-factory/anyOf.ts rename to src/predicate/factory/anyOf.ts index e0cda15..1b793bd 100644 --- a/src/predicate-factory/anyOf.ts +++ b/src/predicate/factory/anyOf.ts @@ -1,5 +1,5 @@ -import type { InferPredicatesReturnType, TypePredicateFn } from '../types/functions.js'; -import type { UnionOf } from '../types/utils.js'; +import type { InferPredicatesReturnType, TypePredicateFn } from '../../types/functions.js'; +import type { UnionOf } from '../../types/utils.js'; /** * Any predicate can pass diff --git a/src/predicate/factory/everyItem.ts b/src/predicate/factory/everyItem.ts new file mode 100644 index 0000000..e295017 --- /dev/null +++ b/src/predicate/factory/everyItem.ts @@ -0,0 +1,6 @@ +import type { TypePredicateFn } from '../../types/functions.js'; + +export const everyItem = + (predicate: TypePredicateFn): TypePredicateFn => + (input: unknown): input is Type[] => + Array.isArray(input) && input.every(predicate); diff --git a/src/predicate-factory/createIsEnumPredicate.ts b/src/predicate/factory/fromEnum.ts similarity index 75% rename from src/predicate-factory/createIsEnumPredicate.ts rename to src/predicate/factory/fromEnum.ts index 0df848d..cd2ef95 100644 --- a/src/predicate-factory/createIsEnumPredicate.ts +++ b/src/predicate/factory/fromEnum.ts @@ -1,5 +1,5 @@ -import type { TypePredicateFn } from '../types/functions.js'; -import type { UnknownRecord } from '../types/records.js'; +import type { TypePredicateFn } from '../../types/functions.js'; +import type { UnknownRecord } from '../../types/records.js'; /** * @internal @@ -27,7 +27,7 @@ const getEnumValues = (enumObject: T): Set => /** * Create a type predicate function that checks if the input is a member of the TypeScript `enum` */ -export const createIsEnumPredicate = (enumObject: T): TypePredicateFn => { +export const fromEnum = (enumObject: T): TypePredicateFn => { const values = getEnumValues(enumObject); return (input: unknown): input is T[keyof T] => values.has(input); diff --git a/src/predicate/factory/index.ts b/src/predicate/factory/index.ts new file mode 100644 index 0000000..2e39b8c --- /dev/null +++ b/src/predicate/factory/index.ts @@ -0,0 +1,18 @@ +export * from './allOf.js'; +export * from './anyOf.js'; +export * from './everyItem.js'; +export * from './fromEnum.js'; +export * from './instanceOf.js'; +export * from './is.js'; +export * from './literal.js'; +export * from './matching.js'; +export * from './maybeArray.js'; +export * from './maybeNull.js'; +export * from './maybeUndefined.js'; +export * from './range.js'; +export * from './shape.js'; +export * from './stringLength.js'; +export * from './tuple.js'; +export * from './typeOf.js'; +export * from './withLength.js'; +export * from './withSize.js'; diff --git a/src/predicate-factory/createIsInstanceOfPredicate.ts b/src/predicate/factory/instanceOf.ts similarity index 50% rename from src/predicate-factory/createIsInstanceOfPredicate.ts rename to src/predicate/factory/instanceOf.ts index 28e392b..4a537b2 100644 --- a/src/predicate-factory/createIsInstanceOfPredicate.ts +++ b/src/predicate/factory/instanceOf.ts @@ -1,7 +1,7 @@ -import type { AnyNewable } from '../types/common.js'; -import type { TypePredicateFn } from '../types/functions.js'; +import type { AnyNewable } from '../../types/common.js'; +import type { TypePredicateFn } from '../../types/functions.js'; -export const createIsInstanceOfPredicate = +export const instanceOf = (constructor: T): TypePredicateFn> => (input: unknown): input is InstanceType => input instanceof constructor; diff --git a/src/predicate-factory/is.ts b/src/predicate/factory/is.ts similarity index 62% rename from src/predicate-factory/is.ts rename to src/predicate/factory/is.ts index 7e001b1..a699859 100644 --- a/src/predicate-factory/is.ts +++ b/src/predicate/factory/is.ts @@ -1,5 +1,5 @@ -import type { TypePredicateFn } from '../types/functions.js'; -import type { Pretty, Writable } from '../types/utils.js'; +import type { TypePredicateFn } from '../../types/functions.js'; +import type { Pretty, Writable } from '../../types/utils.js'; export const is = (...options: T): TypePredicateFn>> => diff --git a/src/predicate/factory/literal.ts b/src/predicate/factory/literal.ts new file mode 100644 index 0000000..f54e269 --- /dev/null +++ b/src/predicate/factory/literal.ts @@ -0,0 +1,6 @@ +import type { TypePredicateFn } from '../../types/functions.js'; + +export const literal = + (value: Type): TypePredicateFn => + (input: unknown): input is Type => + input === value; diff --git a/src/predicate-factory/createStringMatchingPredicate.ts b/src/predicate/factory/matching.ts similarity index 58% rename from src/predicate-factory/createStringMatchingPredicate.ts rename to src/predicate/factory/matching.ts index 7a1e58a..c48f7fd 100644 --- a/src/predicate-factory/createStringMatchingPredicate.ts +++ b/src/predicate/factory/matching.ts @@ -1,6 +1,6 @@ -import type { TypePredicateFn } from '../types/functions.js'; +import type { TypePredicateFn } from '../../types/functions.js'; -export const createStringMatchingPredicate = +export const matching = (pattern: RegExp): TypePredicateFn => (input: unknown): input is T => typeof input === 'string' && pattern.test(input); diff --git a/src/predicate-factory/maybeArray.ts b/src/predicate/factory/maybeArray.ts similarity index 75% rename from src/predicate-factory/maybeArray.ts rename to src/predicate/factory/maybeArray.ts index b5b4b3b..269f387 100644 --- a/src/predicate-factory/maybeArray.ts +++ b/src/predicate/factory/maybeArray.ts @@ -1,4 +1,4 @@ -import type { TypePredicateFn } from '../types/functions.js'; +import type { TypePredicateFn } from '../../types/functions.js'; export const maybeArray = (predicate: TypePredicateFn): TypePredicateFn => diff --git a/src/predicate-factory/maybeNull.ts b/src/predicate/factory/maybeNull.ts similarity index 72% rename from src/predicate-factory/maybeNull.ts rename to src/predicate/factory/maybeNull.ts index 383a71c..58970aa 100644 --- a/src/predicate-factory/maybeNull.ts +++ b/src/predicate/factory/maybeNull.ts @@ -1,4 +1,4 @@ -import type { TypePredicateFn } from '../types/functions.js'; +import type { TypePredicateFn } from '../../types/functions.js'; export const maybeNull = (predicate: TypePredicateFn): TypePredicateFn => diff --git a/src/predicate-factory/maybeUndefined.ts b/src/predicate/factory/maybeUndefined.ts similarity index 75% rename from src/predicate-factory/maybeUndefined.ts rename to src/predicate/factory/maybeUndefined.ts index ddb4095..1f8a66b 100644 --- a/src/predicate-factory/maybeUndefined.ts +++ b/src/predicate/factory/maybeUndefined.ts @@ -1,4 +1,4 @@ -import type { TypePredicateFn } from '../types/functions.js'; +import type { TypePredicateFn } from '../../types/functions.js'; export const maybeUndefined = (predicate: TypePredicateFn): TypePredicateFn => diff --git a/src/predicate/factory/range.ts b/src/predicate/factory/range.ts new file mode 100644 index 0000000..6a02a9e --- /dev/null +++ b/src/predicate/factory/range.ts @@ -0,0 +1,18 @@ +import type { TypePredicateFn } from '../../types/functions.js'; + +export const range = (min: Type, max: Type): TypePredicateFn => { + if (min === Number.NEGATIVE_INFINITY && max === Number.POSITIVE_INFINITY) { + throw new RangeError('min and max cannot be -Infinity and Infinity'); + } + + if (Number.isNaN(min) || Number.isNaN(max)) { + throw new RangeError('min and max must not be NaN'); + } + + if (min >= max) { + throw new RangeError('min must be less than max'); + } + + return (input: unknown): input is Type => + (typeof input === 'number' || typeof input === 'bigint') && input >= min && input <= max; +}; diff --git a/src/predicate-factory/createObjectShapePredicate.ts b/src/predicate/factory/shape.ts similarity index 55% rename from src/predicate-factory/createObjectShapePredicate.ts rename to src/predicate/factory/shape.ts index 42a31b8..0dbba8d 100644 --- a/src/predicate-factory/createObjectShapePredicate.ts +++ b/src/predicate/factory/shape.ts @@ -1,15 +1,19 @@ -import { isAnyObject } from '../predicate/isAnyObject.js'; +/* eslint-disable @typescript-eslint/ban-types */ +import { isAnyObject } from '../isAnyObject.js'; -import { createStringMatchingPredicate } from './createStringMatchingPredicate.js'; +import { literal } from './literal.js'; +import { matching } from './matching.js'; -import type { Primitive } from '../types/common.js'; -import type { InferPredicateReturnType, TypePredicateFn } from '../types/functions.js'; -import type { Pretty } from '../types/utils.js'; +import type { Primitive } from '../../types/common.js'; +import type { InferPredicateReturnType, TypePredicateFn } from '../../types/functions.js'; +import type { Pretty } from '../../types/utils.js'; export type WithRegExp = Type extends string ? Type | RegExp : Type; export type ObjectShapeValue = Type extends object - ? ObjectShapeRecord + ? Type extends Function + ? TypePredicateFn + : ObjectShapeRecord : WithRegExp>; export type ObjectShapeRecord = { @@ -30,24 +34,23 @@ export type InferTypeFromShape> = Pretty -readonly [Property in keyof Shape]: InferTypeFromShapeValue; }>; -export const createObjectShapePredicate = < - Type extends object, - const Shape extends ObjectShapeRecord = ObjectShapeRecord, ->( - shape: Shape, +export const shape = = ObjectShapeRecord>( + objectShape: Shape, ): TypePredicateFn> => { - const entries: [key: string, predicate: TypePredicateFn][] = Object.entries(shape).map(([key, value]) => { - const predicate = - typeof value === 'function' - ? (value as TypePredicateFn) - : value instanceof RegExp - ? createStringMatchingPredicate(value) - : isAnyObject(value) - ? createObjectShapePredicate(value) - : (input: unknown): input is typeof value => input === value; - - return [key, predicate]; - }); + const entries: [key: string, predicate: TypePredicateFn][] = Object.entries(objectShape).map( + ([key, value]) => { + const predicate = + typeof value === 'function' + ? (value as TypePredicateFn) + : value instanceof RegExp + ? matching(value) + : isAnyObject(value) + ? shape(value) + : literal(value); + + return [key, predicate]; + }, + ); return (input: unknown): input is InferTypeFromShape => isAnyObject(input) && entries.every(([key, predicate]) => predicate(input[key])); diff --git a/src/predicate/factory/simple.ts b/src/predicate/factory/simple.ts new file mode 100644 index 0000000..cb29f1d --- /dev/null +++ b/src/predicate/factory/simple.ts @@ -0,0 +1,29 @@ +/* eslint-disable @typescript-eslint/ban-types */ +import type { InferPredicateReturnType, TypePredicateFn } from '../../types/functions.js'; + +export type Simple = Type extends string + ? string + : Type extends number + ? number + : Type extends boolean + ? boolean + : Type extends bigint + ? bigint + : Type extends symbol + ? symbol + : Type extends undefined + ? undefined + : Type extends null + ? null + : Type extends Function + ? Function + : Type extends unknown[] + ? unknown[] + : Type extends object + ? object + : never; + +export const simple = + >(fn: Fn): TypePredicateFn>> => + (input): input is Simple> => + fn(input); diff --git a/src/predicate/factory/stringLength.ts b/src/predicate/factory/stringLength.ts new file mode 100644 index 0000000..88de8a8 --- /dev/null +++ b/src/predicate/factory/stringLength.ts @@ -0,0 +1,16 @@ +import type { TypePredicateFn } from '../../types/functions.js'; + +export const stringLength = ( + min: number, + max: number = Number.POSITIVE_INFINITY, +): TypePredicateFn => { + if (min < 0) { + throw new RangeError('min must be greater than or equal to 0'); + } + + if (min > max) { + throw new RangeError('min cannot be greater than max'); + } + + return (input: unknown): input is Type => typeof input === 'string' && input.length >= min && input.length <= max; +}; diff --git a/src/predicate-factory/createIsTuplePredicate.ts b/src/predicate/factory/tuple.ts similarity index 76% rename from src/predicate-factory/createIsTuplePredicate.ts rename to src/predicate/factory/tuple.ts index 414c3ac..fd1e65f 100644 --- a/src/predicate-factory/createIsTuplePredicate.ts +++ b/src/predicate/factory/tuple.ts @@ -1,5 +1,5 @@ -import type { Primitive } from '../types/common.js'; -import type { TypePredicateFn } from '../types/functions.js'; +import type { Primitive } from '../../types/common.js'; +import type { TypePredicateFn } from '../../types/functions.js'; export type TuplePredicateInput = (Primitive | TypePredicateFn)[]; @@ -7,7 +7,7 @@ export type GetTupleFromInput = { [Property in keyof Type]: Type[Property] extends TypePredicateFn ? T : Type[Property]; }; -export const createIsTuplePredicate = ( +export const tuple = ( tupleShape: Input, ): TypePredicateFn> => { return (input: unknown): input is GetTupleFromInput => diff --git a/src/predicate/factory/typeOf.ts b/src/predicate/factory/typeOf.ts new file mode 100644 index 0000000..1ce5077 --- /dev/null +++ b/src/predicate/factory/typeOf.ts @@ -0,0 +1,6 @@ +import type { TypeOf, TypeOfMapping } from '../../types/common.js'; + +export const typeOf = + (...types: T[]) => + (input: Input): input is Input & TypeOfMapping[T] => + types.some((type) => typeof input === type); diff --git a/src/predicate/factory/withLength.ts b/src/predicate/factory/withLength.ts new file mode 100644 index 0000000..248d5a4 --- /dev/null +++ b/src/predicate/factory/withLength.ts @@ -0,0 +1,36 @@ +import type { InferPredicateReturnType, TypePredicateFn } from '../../types/functions.js'; +import type { NumberRange } from '../../types/numbers.js'; + +export function withLength, Length extends number>( + fn: Fn, + length: Length, +): TypePredicateFn & { length: Length }>; + +export function withLength, const Length extends [number]>( + fn: Fn, + length: Length, +): TypePredicateFn & { length: Length[0] }>; + +export function withLength, Range extends NumberRange>( + fn: Fn, + range: Range, +): TypePredicateFn & { length: number }>; + +export function withLength, LengthOrRange extends number | NumberRange>( + fn: Fn, + lengthRange: LengthOrRange, +): TypePredicateFn & { length: number }> { + if (Array.isArray(lengthRange)) { + const [min = Number.NEGATIVE_INFINITY, max = Number.POSITIVE_INFINITY] = lengthRange; + + if (min > max) { + throw new RangeError('min cannot be greater than max'); + } + + return (input: unknown): input is InferPredicateReturnType & { length: number } => + fn(input) && input.length >= min && input.length <= max; + } + + return (input: unknown): input is InferPredicateReturnType & { length: number } => + fn(input) && input.length === lengthRange; +} diff --git a/src/predicate/factory/withSize.ts b/src/predicate/factory/withSize.ts new file mode 100644 index 0000000..f2aa743 --- /dev/null +++ b/src/predicate/factory/withSize.ts @@ -0,0 +1,36 @@ +import type { InferPredicateReturnType, TypePredicateFn } from '../../types/functions.js'; +import type { NumberRange } from '../../types/numbers.js'; + +export function withSize, Size extends number>( + fn: Fn, + size: Size, +): TypePredicateFn & { size: Size }>; + +export function withSize, const Size extends [number]>( + fn: Fn, + size: Size, +): TypePredicateFn & { size: Size[0] }>; + +export function withSize, Range extends NumberRange>( + fn: Fn, + range: Range, +): TypePredicateFn & { size: number }>; + +export function withSize, SizeOrRange extends number | NumberRange>( + fn: Fn, + sizeRange: SizeOrRange, +): TypePredicateFn & { size: number }> { + if (Array.isArray(sizeRange)) { + const [min = Number.NEGATIVE_INFINITY, max = Number.POSITIVE_INFINITY] = sizeRange; + + if (min > max) { + throw new RangeError('min cannot be greater than max'); + } + + return (input: unknown): input is InferPredicateReturnType & { size: number } => + fn(input) && input.size >= min && input.size <= max; + } + + return (input: unknown): input is InferPredicateReturnType & { size: number } => + fn(input) && input.size === sizeRange; +} diff --git a/src/predicate/isAsyncIterable.ts b/src/predicate/isAsyncIterable.ts index cd13563..24ba37e 100644 --- a/src/predicate/isAsyncIterable.ts +++ b/src/predicate/isAsyncIterable.ts @@ -1,5 +1,8 @@ -import { isObjectWith } from './isObjectWith.js'; - export const isAsyncIterable = (input: unknown): input is AsyncIterable => { - return isObjectWith(input, Symbol.asyncIterator) && typeof input[Symbol.asyncIterator] === 'function'; + return ( + input !== null && + typeof input === 'object' && + Symbol.asyncIterator in input && + typeof input[Symbol.asyncIterator] === 'function' + ); }; diff --git a/src/predicate/isBigIntArray.ts b/src/predicate/isBigIntArray.ts index bd06dc3..9e50cb2 100644 --- a/src/predicate/isBigIntArray.ts +++ b/src/predicate/isBigIntArray.ts @@ -1,5 +1,4 @@ -import { everyItem } from '../predicate-factory/everyItem.js'; - +import { everyItem } from './factory/everyItem.js'; import { isBigInt } from './isBigInt.js'; export const isBigIntArray = everyItem(isBigInt); diff --git a/src/predicate/isBooleanArray.ts b/src/predicate/isBooleanArray.ts index 8b81b5b..597d2f5 100644 --- a/src/predicate/isBooleanArray.ts +++ b/src/predicate/isBooleanArray.ts @@ -1,5 +1,4 @@ -import { everyItem } from '../predicate-factory/everyItem.js'; - +import { everyItem } from './factory/everyItem.js'; import { isBoolean } from './isBoolean.js'; export const isBooleanArray = everyItem(isBoolean); diff --git a/src/predicate/isDigitsString.ts b/src/predicate/isDigitsString.ts index def39dc..5b79e75 100644 --- a/src/predicate/isDigitsString.ts +++ b/src/predicate/isDigitsString.ts @@ -1,5 +1,5 @@ -import { createStringMatchingPredicate } from '../predicate-factory/createStringMatchingPredicate.js'; +import { matching } from './factory/matching.js'; import type { NumericString } from '../types/common.js'; -export const isDigitsString = createStringMatchingPredicate(/^\d+$/); +export const isDigitsString = matching(/^\d+$/); diff --git a/src/predicate/isFunction.ts b/src/predicate/isFunction.ts index 3c14a99..35c9402 100644 --- a/src/predicate/isFunction.ts +++ b/src/predicate/isFunction.ts @@ -1,4 +1,2 @@ -import type { AnyFunction } from '../types/common.js'; - -export const isFunction = (input: unknown): input is Func => - typeof input === 'function'; +/* eslint-disable @typescript-eslint/ban-types */ +export const isFunction = (input: unknown): input is Function => typeof input === 'function'; diff --git a/src/predicate/isISODateString.ts b/src/predicate/isISODateString.ts index 3a0a620..8dbc79e 100644 --- a/src/predicate/isISODateString.ts +++ b/src/predicate/isISODateString.ts @@ -1,8 +1,8 @@ -import { createStringMatchingPredicate } from '../predicate-factory/createStringMatchingPredicate.js'; +import { matching } from './factory/matching.js'; import type { ISODateString } from '../types/date-time.js'; export const ISO_DATE_PATTERN = /^(?[+-]?\d{4,})-(?[01]\d)-(?0[1-9]|[12]\d|3[01])T(?\d{2}):(?\d{2}):(?\d{2})(\.(?\d{3}Z))?$/; -export const isISODateString = createStringMatchingPredicate(ISO_DATE_PATTERN); +export const isISODateString = matching(ISO_DATE_PATTERN); diff --git a/src/predicate/isIntString.ts b/src/predicate/isIntString.ts index b7aaa1a..25ff9ec 100644 --- a/src/predicate/isIntString.ts +++ b/src/predicate/isIntString.ts @@ -1,5 +1,5 @@ -import { createStringMatchingPredicate } from '../predicate-factory/createStringMatchingPredicate.js'; +import { matching } from './factory/matching.js'; import type { NumericString } from '../types/common.js'; -export const isIntString = createStringMatchingPredicate(/^[-+]?\d+$/); +export const isIntString = matching(/^[-+]?\d+$/); diff --git a/src/predicate/isIterable.ts b/src/predicate/isIterable.ts index 3ba3a33..6f0de3a 100644 --- a/src/predicate/isIterable.ts +++ b/src/predicate/isIterable.ts @@ -1,5 +1,8 @@ -import { isObject } from './isObject.js'; - export const isIterable = (input: unknown): input is Iterable => { - return isObject(input) && typeof input[Symbol.iterator] === 'function'; + return ( + input !== null && + typeof input === 'object' && + Symbol.iterator in input && + typeof input[Symbol.iterator] === 'function' + ); }; diff --git a/src/predicate/isNullArray.ts b/src/predicate/isNullArray.ts index b3145b7..bf2fa7b 100644 --- a/src/predicate/isNullArray.ts +++ b/src/predicate/isNullArray.ts @@ -1,5 +1,4 @@ -import { everyItem } from '../predicate-factory/everyItem.js'; - +import { everyItem } from './factory/everyItem.js'; import { isNull } from './isNull.js'; export const isNullArray = everyItem(isNull); diff --git a/src/predicate/isNumberArray.ts b/src/predicate/isNumberArray.ts index c24663e..b3fd098 100644 --- a/src/predicate/isNumberArray.ts +++ b/src/predicate/isNumberArray.ts @@ -1,5 +1,4 @@ -import { everyItem } from '../predicate-factory/everyItem.js'; - +import { everyItem } from './factory/everyItem.js'; import { isNumber } from './isNumber.js'; export const isNumberArray = everyItem(isNumber); diff --git a/src/predicate/isNumericString.ts b/src/predicate/isNumericString.ts index 6ce56f8..1354a9b 100644 --- a/src/predicate/isNumericString.ts +++ b/src/predicate/isNumericString.ts @@ -1,5 +1,5 @@ -import { createStringMatchingPredicate } from '../predicate-factory/createStringMatchingPredicate.js'; +import { matching } from './factory/matching.js'; import type { NumericString } from '../types/common.js'; -export const isNumericString = createStringMatchingPredicate(/^[-+]?\d+(\.\d+)?$/); +export const isNumericString = matching(/^[-+]?\d+(\.\d+)?$/); diff --git a/src/predicate/isNumericValueArray.ts b/src/predicate/isNumericValueArray.ts index 34f1ceb..bd77446 100644 --- a/src/predicate/isNumericValueArray.ts +++ b/src/predicate/isNumericValueArray.ts @@ -1,5 +1,4 @@ -import { everyItem } from '../predicate-factory/everyItem.js'; - +import { everyItem } from './factory/everyItem.js'; import { isNumericValue } from './isNumericValue.js'; export const isNumericValueArray = everyItem(isNumericValue); diff --git a/src/predicate/isOptionalBigInt.ts b/src/predicate/isOptionalBigInt.ts index 7d5f1bb..b70627d 100644 --- a/src/predicate/isOptionalBigInt.ts +++ b/src/predicate/isOptionalBigInt.ts @@ -1,5 +1,4 @@ -import { maybeUndefined } from '../predicate-factory/maybeUndefined.js'; - +import { maybeUndefined } from './factory/maybeUndefined.js'; import { isBigInt } from './isBigInt.js'; export const isOptionalBigInt = maybeUndefined(isBigInt); diff --git a/src/predicate/isOptionalBoolean.ts b/src/predicate/isOptionalBoolean.ts index 59e8a22..b77bd8d 100644 --- a/src/predicate/isOptionalBoolean.ts +++ b/src/predicate/isOptionalBoolean.ts @@ -1,5 +1,4 @@ -import { maybeUndefined } from '../predicate-factory/maybeUndefined.js'; - +import { maybeUndefined } from './factory/maybeUndefined.js'; import { isBoolean } from './isBoolean.js'; export const isOptionalBoolean = maybeUndefined(isBoolean); diff --git a/src/predicate/isOptionalISODateString.ts b/src/predicate/isOptionalISODateString.ts index 33d5fb2..f060994 100644 --- a/src/predicate/isOptionalISODateString.ts +++ b/src/predicate/isOptionalISODateString.ts @@ -1,5 +1,4 @@ -import { maybeUndefined } from '../predicate-factory/maybeUndefined.js'; - +import { maybeUndefined } from './factory/maybeUndefined.js'; import { isISODateString } from './isISODateString.js'; export const isOptionalISODateString = maybeUndefined(isISODateString); diff --git a/src/predicate/isOptionalNull.ts b/src/predicate/isOptionalNull.ts index 5d33b08..4b96cdb 100644 --- a/src/predicate/isOptionalNull.ts +++ b/src/predicate/isOptionalNull.ts @@ -1,5 +1,4 @@ -import { maybeUndefined } from '../predicate-factory/maybeUndefined.js'; - +import { maybeUndefined } from './factory/maybeUndefined.js'; import { isNull } from './isNull.js'; export const isOptionalNull = maybeUndefined(isNull); diff --git a/src/predicate/isOptionalNumber.ts b/src/predicate/isOptionalNumber.ts index 158bc62..3995b0a 100644 --- a/src/predicate/isOptionalNumber.ts +++ b/src/predicate/isOptionalNumber.ts @@ -1,5 +1,4 @@ -import { maybeUndefined } from '../predicate-factory/maybeUndefined.js'; - +import { maybeUndefined } from './factory/maybeUndefined.js'; import { isNumber } from './isNumber.js'; export const isOptionalNumber = maybeUndefined(isNumber); diff --git a/src/predicate/isOptionalString.ts b/src/predicate/isOptionalString.ts index 513ee44..96f2f62 100644 --- a/src/predicate/isOptionalString.ts +++ b/src/predicate/isOptionalString.ts @@ -1,5 +1,4 @@ -import { maybeUndefined } from '../predicate-factory/maybeUndefined.js'; - +import { maybeUndefined } from './factory/maybeUndefined.js'; import { isString } from './isString.js'; export const isOptionalString = maybeUndefined(isString); diff --git a/src/predicate/isOptionalSymbol.ts b/src/predicate/isOptionalSymbol.ts index 15b391a..2b8926a 100644 --- a/src/predicate/isOptionalSymbol.ts +++ b/src/predicate/isOptionalSymbol.ts @@ -1,5 +1,4 @@ -import { maybeUndefined } from '../predicate-factory/maybeUndefined.js'; - +import { maybeUndefined } from './factory/maybeUndefined.js'; import { isSymbol } from './isSymbol.js'; export const isOptionalSymbol = maybeUndefined(isSymbol); diff --git a/src/predicate/isPromiseLike.ts b/src/predicate/isPromiseLike.ts index 63a50a3..4537a46 100644 --- a/src/predicate/isPromiseLike.ts +++ b/src/predicate/isPromiseLike.ts @@ -1,6 +1,7 @@ +import { shape } from './factory/shape.js'; +import { withLength } from './factory/withLength.js'; import { isFunction } from './isFunction.js'; -import { isObjectWith } from './isObjectWith.js'; -export function isPromiseLike(input: unknown): input is PromiseLike { - return isObjectWith(input, 'then') && isFunction(input.then) && input.then.length === 2; -} +export const isPromiseLike = shape>({ + then: withLength(isFunction, 2), +}); diff --git a/src/predicate/isStringArray.ts b/src/predicate/isStringArray.ts index 6de4fef..ccece31 100644 --- a/src/predicate/isStringArray.ts +++ b/src/predicate/isStringArray.ts @@ -1,5 +1,4 @@ -import { everyItem } from '../predicate-factory/everyItem.js'; - +import { everyItem } from './factory/everyItem.js'; import { isString } from './isString.js'; export const isStringArray = everyItem(isString); diff --git a/src/predicate/isSymbolArray.ts b/src/predicate/isSymbolArray.ts index 0d0bd84..ed7b11a 100644 --- a/src/predicate/isSymbolArray.ts +++ b/src/predicate/isSymbolArray.ts @@ -1,5 +1,4 @@ -import { everyItem } from '../predicate-factory/everyItem.js'; - +import { everyItem } from './factory/everyItem.js'; import { isSymbol } from './isSymbol.js'; export const isSymbolArray = everyItem(isSymbol); diff --git a/src/predicate/isUndefinedArray.ts b/src/predicate/isUndefinedArray.ts index bd9be5a..f53e840 100644 --- a/src/predicate/isUndefinedArray.ts +++ b/src/predicate/isUndefinedArray.ts @@ -1,5 +1,4 @@ -import { everyItem } from '../predicate-factory/everyItem.js'; - +import { everyItem } from './factory/everyItem.js'; import { isUndefined } from './isUndefined.js'; export const isUndefinedArray = everyItem(isUndefined); diff --git a/src/types/common.ts b/src/types/common.ts index d0334d3..4886aff 100644 --- a/src/types/common.ts +++ b/src/types/common.ts @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/no-explicit-any,@typescript-eslint/ban-types */ import type { NotPromise } from './utils.js'; @@ -16,7 +16,17 @@ export type JsonValue = string | number | boolean | null | object | JsonObject | export type TypeOf = 'undefined' | 'object' | 'boolean' | 'number' | 'bigint' | 'string' | 'symbol' | 'function'; -// eslint-disable-next-line @typescript-eslint/ban-types +export type TypeOfMapping = { + undefined: undefined; + object: object; + boolean: boolean; + number: number; + bigint: bigint; + string: string; + symbol: symbol; + function: Function; +}; + export type Builtin = Date | Error | Function | Primitive | RegExp; export type AnyFunction = (...args: any[]) => any; diff --git a/src/types/numbers.ts b/src/types/numbers.ts index 65bb36c..adfa774 100644 --- a/src/types/numbers.ts +++ b/src/types/numbers.ts @@ -1,3 +1,9 @@ import type { Branded } from './branded.js'; export type Int32 = Branded; + +export type NumberRange = + | [min: number] + | [min: number, max: undefined] + | [min: number, max: number] + | [min: undefined, max: number]; diff --git a/test/predicate-factory/createNumberRangePredicate.test.ts b/test/predicate-factory/createNumberRangePredicate.test.ts deleted file mode 100644 index 4064419..0000000 --- a/test/predicate-factory/createNumberRangePredicate.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { describe, it, expect } from 'vitest'; - -import { createNumberRangePredicate } from '../../src/predicate-factory/createNumberRangePredicate.js'; - -describe('createNumberRangePredicate()', () => { - it('Returns a type predicate function', () => { - const fn = createNumberRangePredicate(1, 10); - - expect(fn).toBeInstanceOf(Function); - expect(fn(1)).toBeTruthy(); - expect(fn(11)).toBeFalsy(); - expect(fn('')).toBeFalsy(); - expect(fn(undefined)).toBeFalsy(); - }); - - it('Max is optional', () => { - const fn = createNumberRangePredicate(1); - - expect(fn(1)).toBeTruthy(); - expect(fn(Number.POSITIVE_INFINITY)).toBeTruthy(); - expect(fn(Number.NEGATIVE_INFINITY)).toBeFalsy(); - }); - - it.each([ - [1, Number.NaN], - [Number.NaN, 10], - [Number.NaN, Number.NaN], - [Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY], - [10, 1], - [1, 1], - ])('Invalid ranges throw - min: %o max: %o', (min, max) => { - expect(() => createNumberRangePredicate(min, max)).toThrowError(); - }); -}); diff --git a/test/predicate-factory/createObjectShapePredicate.test.ts b/test/predicate-factory/createObjectShapePredicate.test.ts deleted file mode 100644 index db8ec6d..0000000 --- a/test/predicate-factory/createObjectShapePredicate.test.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { describe, expect, it } from 'vitest'; - -import { isNumber } from '../../src/index.js'; -import { isOptionalString } from '../../src/predicate/isOptionalString.js'; -import { isString } from '../../src/predicate/isString.js'; -import { createObjectShapePredicate } from '../../src/predicate-factory/createObjectShapePredicate.js'; - -describe('createObjectShapePredicate', () => { - type User = { - name: string; - age?: number; - contact: { - email: string; - phone?: string; - }; - tuple: [key: string, value: number]; - }; - - it('Returns a type predicate function', () => { - expect(createObjectShapePredicate({})).toBeInstanceOf(Function); - }); - - it('Accepts an object shape record', () => { - const fn = createObjectShapePredicate({ - name: /^Test$/, - age: isNumber, - contact: { - email: isString, - phone: isOptionalString, - }, - tuple: ['PI', Math.PI], - }); - - expect(fn('')).toBeFalsy(); - expect(fn({})).toBeFalsy(); - - expect( - fn({ - name: 'Name', - age: 99, - title: null, - }), - ).toBeFalsy(); - - expect( - fn({ - name: 'Test', - age: 100, - contact: { - email: 'test@example.com', - phone: undefined, - }, - tuple: ['PI', Math.PI], - }), - ).toBeTruthy(); - }); -}); diff --git a/test/predicate-factory/createStringLengthRangePredicate.test.ts b/test/predicate-factory/createStringLengthRangePredicate.test.ts deleted file mode 100644 index d773429..0000000 --- a/test/predicate-factory/createStringLengthRangePredicate.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { describe, it, expect } from 'vitest'; - -import { createStringLengthRangePredicate } from '../../src/predicate-factory/createStringLengthRangePredicate.js'; - -import type { AlphanumericCharacter } from '../../src/index.js'; - -describe('createStringLengthRangePredicate()', () => { - it('Returns a type predicate function', () => { - const fn = createStringLengthRangePredicate(1, 10); - - expect(fn).toBeInstanceOf(Function); - expect(fn('a')).toBeTruthy(); - expect(fn('')).toBeFalsy(); - expect(fn(undefined)).toBeFalsy(); - expect(fn('a'.repeat(20))).toBeFalsy(); - }); - - it('Max is optional', () => { - const fn = createStringLengthRangePredicate(1); - - expect(fn('a'.repeat(20))).toBeTruthy(); - }); - - it('Min must be >= 0', () => { - expect(() => createStringLengthRangePredicate(-1)).toThrow(RangeError); - }); -}); diff --git a/test/predicate-factory/allOf.test.ts b/test/predicate/factory/allOf.test.ts similarity index 60% rename from test/predicate-factory/allOf.test.ts rename to test/predicate/factory/allOf.test.ts index fae4d66..d66877d 100644 --- a/test/predicate-factory/allOf.test.ts +++ b/test/predicate/factory/allOf.test.ts @@ -1,9 +1,9 @@ import { describe, it, expect } from 'vitest'; -import { isNumber } from '../../src/predicate/isNumber.js'; -import { isString } from '../../src/predicate/isString.js'; -import { allOf } from '../../src/predicate-factory/allOf.js'; -import { createObjectShapePredicate } from '../../src/predicate-factory/createObjectShapePredicate.js'; +import { allOf } from '../../../src/predicate/factory/allOf.js'; +import { shape } from '../../../src/predicate/factory/shape.js'; +import { isNumber } from '../../../src/predicate/isNumber.js'; +import { isString } from '../../../src/predicate/isString.js'; describe('allOf()', () => { it('Requires one or more type predicate function', () => { @@ -12,10 +12,10 @@ describe('allOf()', () => { it('Returns a type predicate function', () => { const fn = allOf( - createObjectShapePredicate({ + shape({ name: isString, }), - createObjectShapePredicate({ + shape({ age: isNumber, }), ); diff --git a/test/predicate-factory/anyOf.test.ts b/test/predicate/factory/anyOf.test.ts similarity index 88% rename from test/predicate-factory/anyOf.test.ts rename to test/predicate/factory/anyOf.test.ts index 8b3fda8..0ffe3e6 100644 --- a/test/predicate-factory/anyOf.test.ts +++ b/test/predicate/factory/anyOf.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect } from 'vitest'; -import { anyOf } from '../../src/predicate-factory/anyOf.js'; +import { anyOf } from '../../../src/predicate/factory/anyOf.js'; describe('anyOf()', () => { it('Requires one or more type predicate function', () => { diff --git a/test/predicate/factory/everyItem.test.ts b/test/predicate/factory/everyItem.test.ts new file mode 100644 index 0000000..3f6051a --- /dev/null +++ b/test/predicate/factory/everyItem.test.ts @@ -0,0 +1,22 @@ +import { describe, it, expect, vi } from 'vitest'; + +import { everyItem } from '../../../src/predicate/factory/everyItem.js'; +import { isBoolean } from '../../../src/predicate/isBoolean.js'; + +describe('everyItem()', () => { + it('Returns a type predicate function', () => { + expect(everyItem(isBoolean)).toBeInstanceOf(Function); + }); + + it('Calls a type predicate function', () => { + const predicate = vi.fn(isBoolean) as unknown as typeof isBoolean; + + const fn = everyItem(predicate); + + expect(fn([true])).toBeTruthy(); + expect(fn([false])).toBeTruthy(); + expect(fn(['test'])).toBeFalsy(); + expect(fn(true)).toBeFalsy(); + expect(predicate).toHaveBeenCalledTimes(3); + }); +}); diff --git a/test/predicate-factory/createIsEnumPredicate.test.ts b/test/predicate/factory/fromEnum.test.ts similarity index 85% rename from test/predicate-factory/createIsEnumPredicate.test.ts rename to test/predicate/factory/fromEnum.test.ts index ce6c2a8..60d554f 100644 --- a/test/predicate-factory/createIsEnumPredicate.test.ts +++ b/test/predicate/factory/fromEnum.test.ts @@ -1,8 +1,8 @@ import { describe, it, expect } from 'vitest'; -import { createIsEnumPredicate } from '../../src/predicate-factory/createIsEnumPredicate.js'; +import { fromEnum } from '../../../src/predicate/factory/fromEnum.js'; -describe('createIsEnumPredicate()', () => { +describe('fromEnum()', () => { describe('enum with string values', () => { enum StringEnum { A = 'a', @@ -10,7 +10,7 @@ describe('createIsEnumPredicate()', () => { C = 'c', } - const fn = createIsEnumPredicate(StringEnum); + const fn = fromEnum(StringEnum); it.each(Object.values(StringEnum))('Returns true for valid input: %s', (value) => { expect(fn(value)).toBeTruthy(); @@ -33,7 +33,7 @@ describe('createIsEnumPredicate()', () => { NotANumber = Number.NaN, } - const fn = createIsEnumPredicate(Demo); + const fn = fromEnum(Demo); it('Returns a type predicate function', () => { expect(fn).toBeInstanceOf(Function); diff --git a/test/predicate-factory/createIsInstanceOfPredicate.test.ts b/test/predicate/factory/instanceOf.test.ts similarity index 52% rename from test/predicate-factory/createIsInstanceOfPredicate.test.ts rename to test/predicate/factory/instanceOf.test.ts index 54832e3..74e5e6e 100644 --- a/test/predicate-factory/createIsInstanceOfPredicate.test.ts +++ b/test/predicate/factory/instanceOf.test.ts @@ -1,10 +1,10 @@ import { describe, it, expect } from 'vitest'; -import { createIsInstanceOfPredicate } from '../../src/predicate-factory/createIsInstanceOfPredicate.js'; +import { instanceOf } from '../../../src/predicate/factory/instanceOf.js'; -describe('createIsInstanceOfPredicate()', () => { +describe('instanceOf()', () => { it('Returns a type predicate function', () => { - const fn = createIsInstanceOfPredicate(Date); + const fn = instanceOf(Date); expect(fn).toBeInstanceOf(Function); expect(fn(new Date())).toBeTruthy(); diff --git a/test/predicate-factory/is.test.ts b/test/predicate/factory/is.test.ts similarity index 91% rename from test/predicate-factory/is.test.ts rename to test/predicate/factory/is.test.ts index 7e46b27..2ed2ebf 100644 --- a/test/predicate-factory/is.test.ts +++ b/test/predicate/factory/is.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect } from 'vitest'; -import { is } from '../../src/predicate-factory/is.js'; +import { is } from '../../../src/predicate/factory/is.js'; describe('is()', () => { it('Returns a type predicate function', () => { diff --git a/test/predicate/factory/literal.test.ts b/test/predicate/factory/literal.test.ts new file mode 100644 index 0000000..bf7ebde --- /dev/null +++ b/test/predicate/factory/literal.test.ts @@ -0,0 +1,14 @@ +import { describe, it, expect } from 'vitest'; + +import { literal } from '../../../src/predicate/factory/literal.js'; + +describe('literal()', () => { + it('Returns a type predicate function', () => { + const fn = literal('test'); + + expect(fn).instanceOf(Function); + expect(fn('test')).toBeTruthy(); + expect(fn(false)).toBeFalsy(); + expect(fn(0)).toBeFalsy(); + }); +}); diff --git a/test/predicate/factory/matching.test.ts b/test/predicate/factory/matching.test.ts new file mode 100644 index 0000000..e729403 --- /dev/null +++ b/test/predicate/factory/matching.test.ts @@ -0,0 +1,19 @@ +import { describe, it, expect } from 'vitest'; + +import { matching } from '../../../src/predicate/factory/matching.js'; + +describe('matching()', () => { + const pattern = /.+/; + + it('Returns a type predicate function', () => { + expect(matching(pattern)).toBeInstanceOf(Function); + }); + + it('Checks if input matches the RegExp', () => { + const fn = matching(pattern); + + expect(fn('Test')).toBeTruthy(); + expect(fn('')).toBeFalsy(); + expect(fn(false)).toBeFalsy(); + }); +}); diff --git a/test/predicate/factory/maybeArray.test.ts b/test/predicate/factory/maybeArray.test.ts new file mode 100644 index 0000000..c87cb61 --- /dev/null +++ b/test/predicate/factory/maybeArray.test.ts @@ -0,0 +1,21 @@ +import { describe, it, expect, vi } from 'vitest'; + +import { maybeArray } from '../../../src/predicate/factory/maybeArray.js'; +import { isBoolean } from '../../../src/predicate/isBoolean.js'; + +describe('maybeArray()', () => { + it('Returns a type predicate function', () => { + expect(maybeArray(isBoolean)).toBeInstanceOf(Function); + }); + + it('Calls a type predicate function', () => { + const predicate = vi.fn(isBoolean) as unknown as typeof isBoolean; + + const fn = maybeArray(predicate); + + expect(fn([true])).toBeTruthy(); + expect(fn([false])).toBeTruthy(); + expect(fn(true)).toBeTruthy(); + expect(predicate).toHaveBeenCalledTimes(3); + }); +}); diff --git a/test/predicate/factory/maybeNull.test.ts b/test/predicate/factory/maybeNull.test.ts new file mode 100644 index 0000000..7602706 --- /dev/null +++ b/test/predicate/factory/maybeNull.test.ts @@ -0,0 +1,21 @@ +import { describe, it, expect, vi } from 'vitest'; + +import { maybeNull } from '../../../src/predicate/factory/maybeNull.js'; +import { isBoolean } from '../../../src/predicate/isBoolean.js'; + +describe('maybeNull()', () => { + it('Returns a type predicate function', () => { + expect(maybeNull(isBoolean)).toBeInstanceOf(Function); + }); + + it('Calls a type predicate function', () => { + const predicate = vi.fn(isBoolean) as unknown as typeof isBoolean; + + const fn = maybeNull(predicate); + + expect(fn(true)).toBeTruthy(); + expect(fn(null)).toBeTruthy(); + expect(fn(false)).toBeTruthy(); + expect(predicate).toHaveBeenCalledTimes(2); + }); +}); diff --git a/test/predicate/factory/maybeUndefined.test.ts b/test/predicate/factory/maybeUndefined.test.ts new file mode 100644 index 0000000..6397318 --- /dev/null +++ b/test/predicate/factory/maybeUndefined.test.ts @@ -0,0 +1,21 @@ +import { describe, it, expect, vi } from 'vitest'; + +import { maybeUndefined } from '../../../src/predicate/factory/maybeUndefined.js'; +import { isBoolean } from '../../../src/predicate/isBoolean.js'; + +describe('maybeUndefined()', () => { + it('Returns a type predicate function', () => { + expect(maybeUndefined(isBoolean)).toBeInstanceOf(Function); + }); + + it('Calls a type predicate function', () => { + const predicate = vi.fn(isBoolean) as unknown as typeof isBoolean; + + const fn = maybeUndefined(predicate); + + expect(fn(true)).toBeTruthy(); + expect(fn(undefined)).toBeTruthy(); + expect(fn(false)).toBeTruthy(); + expect(predicate).toHaveBeenCalledTimes(2); + }); +}); diff --git a/test/predicate/factory/range.test.ts b/test/predicate/factory/range.test.ts new file mode 100644 index 0000000..add7e8f --- /dev/null +++ b/test/predicate/factory/range.test.ts @@ -0,0 +1,33 @@ +import { describe, it, expect } from 'vitest'; + +import { range } from '../../../src/predicate/factory/range.js'; + +describe('range()', () => { + const fn = range(1, 4); + + it('Returns a type predicate function', () => { + expect(fn).toBeInstanceOf(Function); + }); + + it.each([1, 2, 3, 4])('Returns true for input in range: %d', (input) => { + expect(fn(input)).toBeTruthy(); + }); + + it.each([0, 5, Number.POSITIVE_INFINITY, Number.NaN, 'test', false, null])( + 'Returns false for input not in range: %s', + (input) => { + expect(fn(input)).toBeFalsy(); + }, + ); + + it.each([ + [1, Number.NaN], + [Number.NaN, 10], + [Number.NaN, Number.NaN], + [Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY], + [10, 1], + [1, 1], + ])('Invalid ranges throw - min: %o max: %o', (min, max) => { + expect(() => range(min, max)).toThrowError(); + }); +}); diff --git a/test/predicate/factory/shape.test-d.ts b/test/predicate/factory/shape.test-d.ts new file mode 100644 index 0000000..9ebfe23 --- /dev/null +++ b/test/predicate/factory/shape.test-d.ts @@ -0,0 +1,89 @@ +import { describe, expectTypeOf, it } from 'vitest'; + +import { range, isNumber } from '../../../src/index.js'; +import { shape, type InferTypeFromShape, type ObjectShapeRecord } from '../../../src/predicate/factory/shape.js'; +import { isOptionalString } from '../../../src/predicate/isOptionalString.js'; +import { isString } from '../../../src/predicate/isString.js'; + +describe('shape()', () => { + enum Role { + Admin = 'admin', + User = 'user', + } + + type User = { + name: string; + role: Role; + value: number; + age?: number; + contact: { + email: string; + phone?: string; + }; + tuple: [key: string, value: number]; + }; + + const user: unknown = { + name: 'Test', + role: Role.User, + value: 50, + age: 100, + contact: { + email: 'test@example.com', + phone: undefined, + }, + tuple: ['PI', Math.PI], + } satisfies User; + + type UserShape = ObjectShapeRecord; + + const userShape = { + name: /^Test$/, + role: Role.User, + value: range(0, 100), + age: isNumber, + contact: { + email: isString, + phone: isOptionalString, + }, + tuple: ['PI', Math.PI], + } satisfies UserShape; + + type InferredUserType = InferTypeFromShape; + type InferredUserTypeConst = InferTypeFromShape; + + it('Returns a type predicate function', () => { + const fn = shape(userShape); + + expectTypeOf(fn).toBeFunction(); + expectTypeOf(fn).parameter(0).toEqualTypeOf(); + }); + + describe('Type parameters', () => { + it('Infers type parameter values', () => { + const fn = shape(userShape); + + if (fn(user)) { + expectTypeOf(user).toEqualTypeOf(); + } + }); + + it('Can provide the Type to constrain the Shape', () => { + const fn = shape(userShape); + + if (fn(user)) { + expectTypeOf(user).toEqualTypeOf(); + } + }); + + it('Can provide the Type and Shape parameters', () => { + const fn = shape<{ name: string }, { name: string | RegExp }>({ + name: 'test', + }); + + if (fn(user)) { + expectTypeOf(user).toEqualTypeOf<{ name: string }>(); + } + }); + }); +}); diff --git a/test/predicate-factory/createObjectShapePredicate.test-d.ts b/test/predicate/factory/shape.test.ts similarity index 69% rename from test/predicate-factory/createObjectShapePredicate.test-d.ts rename to test/predicate/factory/shape.test.ts index 24776ef..876a2f6 100644 --- a/test/predicate-factory/createObjectShapePredicate.test-d.ts +++ b/test/predicate/factory/shape.test.ts @@ -1,15 +1,12 @@ import { describe, expectTypeOf, it } from 'vitest'; -import { createNumberRangePredicate, isNumber } from '../../src/index.js'; -import { isOptionalString } from '../../src/predicate/isOptionalString.js'; -import { isString } from '../../src/predicate/isString.js'; -import { - createObjectShapePredicate, - type InferTypeFromShape, - type ObjectShapeRecord, -} from '../../src/predicate-factory/createObjectShapePredicate.js'; +import { range } from '../../../src/predicate/factory/range.js'; +import { shape, type InferTypeFromShape, type ObjectShapeRecord } from '../../../src/predicate/factory/shape.js'; +import { isNumber } from '../../../src/predicate/isNumber.js'; +import { isOptionalString } from '../../../src/predicate/isOptionalString.js'; +import { isString } from '../../../src/predicate/isString.js'; -describe('createObjectShapePredicate', () => { +describe('shape()', () => { enum Role { Admin = 'admin', User = 'user', @@ -44,7 +41,7 @@ describe('createObjectShapePredicate', () => { const userShape = { name: /^Test$/, role: Role.User, - value: createNumberRangePredicate(0, 100), + value: range(0, 100), age: isNumber, contact: { email: isString, @@ -57,7 +54,7 @@ describe('createObjectShapePredicate', () => { type InferredUserTypeConst = InferTypeFromShape; it('Returns a type predicate function', () => { - const fn = createObjectShapePredicate(userShape); + const fn = shape(userShape); expectTypeOf(fn).toBeFunction(); expectTypeOf(fn).parameter(0).toMatchTypeOf(); @@ -65,7 +62,7 @@ describe('createObjectShapePredicate', () => { describe('Type parameters', () => { it('Infers type parameter values', () => { - const fn = createObjectShapePredicate(userShape); + const fn = shape(userShape); if (fn(user)) { expectTypeOf(user).toMatchTypeOf(); @@ -73,7 +70,7 @@ describe('createObjectShapePredicate', () => { }); it('Can provide the Type to constrain the Shape', () => { - const fn = createObjectShapePredicate(userShape); + const fn = shape(userShape); if (fn(user)) { expectTypeOf(user).toMatchTypeOf(); @@ -81,7 +78,7 @@ describe('createObjectShapePredicate', () => { }); it('Can provide the Type and Shape parameters', () => { - const fn = createObjectShapePredicate<{ name: string }, { name: string | RegExp }>({ + const fn = shape<{ name: string }, { name: string | RegExp }>({ name: 'test', }); diff --git a/test/predicate/factory/simple.test-d.ts b/test/predicate/factory/simple.test-d.ts new file mode 100644 index 0000000..1dd2275 --- /dev/null +++ b/test/predicate/factory/simple.test-d.ts @@ -0,0 +1,47 @@ +/* eslint-disable @typescript-eslint/ban-types */ +import { describe, expectTypeOf, it } from 'vitest'; + +import { allOf } from '../../../src/predicate/factory/allOf.js'; +import { anyOf } from '../../../src/predicate/factory/anyOf.js'; +import { literal } from '../../../src/predicate/factory/literal.js'; +import { simple } from '../../../src/predicate/factory/simple.js'; +import { withLength } from '../../../src/predicate/factory/withLength.js'; + +import type { TypePredicateFn } from '../../../src/types/functions.js'; + +describe('simple()', () => { + it('Returns a type predicate function', () => { + const fn = simple(literal(true)); + + expectTypeOf(fn).toBeFunction(); + expectTypeOf(fn).parameter(0).toEqualTypeOf(); + }); + + it('Returns a type predicate function using simplified types', () => { + expectTypeOf(simple(literal(true))).toEqualTypeOf>(); + + expectTypeOf(simple(literal('test'))).toEqualTypeOf>(); + + expectTypeOf(simple(literal(123))).toEqualTypeOf>(); + + expectTypeOf(simple(literal(456n))).toEqualTypeOf>(); + + expectTypeOf(simple(literal(Symbol.for('test')))).toEqualTypeOf>(); + + expectTypeOf(simple(literal(undefined))).toEqualTypeOf>(); + + expectTypeOf(simple(literal(null))).toEqualTypeOf>(); + + expectTypeOf(simple(literal({}))).toEqualTypeOf>(); + + expectTypeOf(simple(literal([]))).toEqualTypeOf>(); + + expectTypeOf(simple(literal(() => true))).toEqualTypeOf>(); + + expectTypeOf(simple(anyOf(literal(true), literal('test')))).toEqualTypeOf>(); + + expectTypeOf(simple(allOf(literal([1, 2, 3, 4]), withLength(Array.isArray, 4)))).toEqualTypeOf< + TypePredicateFn + >(); + }); +}); diff --git a/test/predicate/factory/simple.test.ts b/test/predicate/factory/simple.test.ts new file mode 100644 index 0000000..0a8575f --- /dev/null +++ b/test/predicate/factory/simple.test.ts @@ -0,0 +1,24 @@ +import { describe, it, expect, vi } from 'vitest'; + +import { literal } from '../../../src/predicate/factory/literal.js'; +import { simple } from '../../../src/predicate/factory/simple.js'; + +import type { TypePredicateFn } from '../../../src/types/functions.js'; + +describe('simple()', () => { + it('Returns a type predicate function', () => { + expect(simple(literal(true))).toBeInstanceOf(Function); + }); + + it('Calls a type predicate function', () => { + const predicate = vi.fn(literal(true)) as unknown as TypePredicateFn; + + const fn = simple(predicate); + + expect(fn(true)).toBeTruthy(); + expect(predicate).toHaveBeenCalledTimes(1); + + expect(fn(null)).toBeFalsy(); + expect(predicate).toHaveBeenCalledTimes(2); + }); +}); diff --git a/test/predicate/factory/stringLength.test.ts b/test/predicate/factory/stringLength.test.ts new file mode 100644 index 0000000..3eba23e --- /dev/null +++ b/test/predicate/factory/stringLength.test.ts @@ -0,0 +1,31 @@ +import { describe, it, expect } from 'vitest'; + +import { stringLength } from '../../../src/predicate/factory/stringLength.js'; + +import type { AlphanumericCharacter } from '../../../src/index.js'; + +describe('stringLength()', () => { + it('Returns a type predicate function', () => { + const fn = stringLength(1, 10); + + expect(fn).toBeInstanceOf(Function); + expect(fn('a')).toBeTruthy(); + expect(fn('')).toBeFalsy(); + expect(fn(undefined)).toBeFalsy(); + expect(fn('a'.repeat(20))).toBeFalsy(); + }); + + it('Max is optional', () => { + const fn = stringLength(1); + + expect(fn('a'.repeat(20))).toBeTruthy(); + }); + + it('Min must be >= 0', () => { + expect(() => stringLength(-1)).toThrow(RangeError); + }); + + it('Min must be less than max', () => { + expect(() => stringLength(100, 0)).toThrow(RangeError); + }); +}); diff --git a/test/predicate-factory/createIsTuplePredicate.test.ts b/test/predicate/factory/tuple.test.ts similarity index 59% rename from test/predicate-factory/createIsTuplePredicate.test.ts rename to test/predicate/factory/tuple.test.ts index 2706b88..d2380d1 100644 --- a/test/predicate-factory/createIsTuplePredicate.test.ts +++ b/test/predicate/factory/tuple.test.ts @@ -1,36 +1,36 @@ import { describe, it, expect } from 'vitest'; -import { isFiniteNumber, isString } from '../../src/index.js'; -import { createIsTuplePredicate } from '../../src/predicate-factory/createIsTuplePredicate.js'; +import { isFiniteNumber, isString } from '../../../src/index.js'; +import { tuple } from '../../../src/predicate/factory/tuple.js'; -describe('createIsTuplePredicate()', () => { +describe('tuple()', () => { it('Returns a type predicate function', () => { - expect(createIsTuplePredicate([])).toBeInstanceOf(Function); + expect(tuple([])).toBeInstanceOf(Function); }); describe('Can define the tuple shape', () => { it('Empty tuple shape returns true', () => { - const fn = createIsTuplePredicate([]); + const fn = tuple([]); expect(fn([])).toBeTruthy(); }); it('Can be made up of primitives', () => { - const fn = createIsTuplePredicate([null, 'test', true, Math.PI, undefined]); + const fn = tuple([null, 'test', true, Math.PI, undefined]); expect(fn([null, 'test', true, Math.PI, undefined])).toBeTruthy(); expect(fn('Fail')).toBeFalsy(); }); it('Can be made up of type predicate functions', () => { - const fn = createIsTuplePredicate([isString, isFiniteNumber]); + const fn = tuple([isString, isFiniteNumber]); expect(fn(['test', 123])).toBeTruthy(); expect(fn([null])).toBeFalsy(); }); it('Can use a mix of primitives and type predicate functions', () => { - const fn = createIsTuplePredicate([isString, null, false, 'test']); + const fn = tuple([isString, null, false, 'test']); expect(fn(['some string', null, false, 'test'])).toBeTruthy(); expect(fn([true, null, false, 'test'])).toBeFalsy(); diff --git a/test/predicate/factory/typeOf.test.ts b/test/predicate/factory/typeOf.test.ts new file mode 100644 index 0000000..bf067c6 --- /dev/null +++ b/test/predicate/factory/typeOf.test.ts @@ -0,0 +1,14 @@ +import { describe, it, expect } from 'vitest'; + +import { typeOf } from '../../../src/predicate/factory/typeOf.js'; + +describe('typeOf()', () => { + it('Returns a type predicate function', () => { + const fn = typeOf('boolean'); + + expect(fn).toBeInstanceOf(Function); + expect(fn(true)).toBeTruthy(); + expect(fn('')).toBeFalsy(); + expect(fn(undefined)).toBeFalsy(); + }); +}); diff --git a/test/predicate/factory/withLength.test.ts b/test/predicate/factory/withLength.test.ts new file mode 100644 index 0000000..48ec3a6 --- /dev/null +++ b/test/predicate/factory/withLength.test.ts @@ -0,0 +1,36 @@ +import { describe, it, expect } from 'vitest'; + +import { withLength } from '../../../src/predicate/factory/withLength.js'; +import { isString } from '../../../src/predicate/isString.js'; + +describe('withLength()', () => { + const twoCharString = withLength(isString, 2); + const stringRange = withLength(isString, [1, 3]); + + it('Returns a type predicate function', () => { + expect(twoCharString).toBeInstanceOf(Function); + expect(stringRange).toBeInstanceOf(Function); + }); + + it('Throws when given an invalid range', () => { + expect(() => { + withLength(isString, [100, 1]); + }).toThrowError(); + }); + + it('Returns true for strings with the specified length', () => { + expect(twoCharString('ab')).toBeTruthy(); + }); + + it.each(['a', 'ab', 'abc'])('Returns true for strings within the specified length range: %s', (input) => { + expect(stringRange(input)).toBeTruthy(); + }); + + it.each(['a', 'abc', true, null, 123])('Returns false for inputs with wrong lengths: %s', (input) => { + expect(twoCharString(input)).toBeFalsy(); + }); + + it.each(['', 'abcd', true, null, 123])('Returns false for inputs outside the specified range: %s', (input) => { + expect(twoCharString(input)).toBeFalsy(); + }); +}); diff --git a/test/predicate/factory/withSize.test.ts b/test/predicate/factory/withSize.test.ts new file mode 100644 index 0000000..26c91e2 --- /dev/null +++ b/test/predicate/factory/withSize.test.ts @@ -0,0 +1,44 @@ +import { describe, it, expect } from 'vitest'; + +import { instanceOf } from '../../../src/index.js'; +import { withSize } from '../../../src/predicate/factory/withSize.js'; + +describe('withSize()', () => { + const twoItemSet = withSize(instanceOf(Set), 2); + const isSetWithRange = withSize(instanceOf(Set), [1, 3]); + + it('Returns a type predicate function', () => { + expect(twoItemSet).toBeInstanceOf(Function); + }); + + it('Throws when given an invalid range', () => { + expect(() => { + withSize(instanceOf(Set), [100, 1]); + }).toThrowError(); + }); + + it('Returns true for strings with the specified length', () => { + expect(twoItemSet(new Set([0, 1]))).toBeTruthy(); + }); + + it.each([new Set([0]), new Set([0, 1]), new Set([0, 1, 2])])( + 'Returns true for strings within the specified size range: %s', + (input) => { + expect(isSetWithRange(input)).toBeTruthy(); + }, + ); + + it.each([new Set([0]), new Set([0, 1, 2]), true, null, 123])( + 'Returns false for inputs with wrong lengths: %s', + (input) => { + expect(twoItemSet(input)).toBeFalsy(); + }, + ); + + it.each([new Set(), new Set([0, 1, 2, 3]), 'test', true, null, 123])( + 'Returns false for inputs outside the specified range: %s', + (input) => { + expect(isSetWithRange(input)).toBeFalsy(); + }, + ); +}); diff --git a/test/type-predicate-factory.test.ts b/test/type-predicate-factory.test.ts deleted file mode 100644 index 4890b4f..0000000 --- a/test/type-predicate-factory.test.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { describe, it, expect, vi } from 'vitest'; - -import { isBoolean } from '../src/predicate/isBoolean.js'; -import { - createStringMatchingPredicate, - everyItem, - maybeArray, - maybeNull, - maybeUndefined, -} from '../src/predicate-factory/index.js'; - -describe('createStringMatchingPredicate()', () => { - const pattern = /.+/; - - it('Returns a type predicate function', () => { - expect(createStringMatchingPredicate(pattern)).toBeInstanceOf(Function); - }); - - it('Predicate function checks if input is a member of the enum', () => { - const fn = createStringMatchingPredicate(pattern); - - expect(fn('Test')).toBeTruthy(); - expect(fn('')).toBeFalsy(); - expect(fn(false)).toBeFalsy(); - }); -}); - -describe('everyItem()', () => { - it('Returns a type predicate function', () => { - expect(everyItem(isBoolean)).toBeInstanceOf(Function); - }); - - it('Calls a type predicate function', () => { - const predicate = vi.fn(isBoolean) as unknown as typeof isBoolean; - - const fn = everyItem(predicate); - - expect(fn([true])).toBeTruthy(); - expect(fn([false])).toBeTruthy(); - expect(fn(['test'])).toBeFalsy(); - expect(fn(true)).toBeFalsy(); - expect(predicate).toHaveBeenCalledTimes(3); - }); -}); - -describe('maybeArray()', () => { - it('Returns a type predicate function', () => { - expect(maybeArray(isBoolean)).toBeInstanceOf(Function); - }); - - it('Calls a type predicate function', () => { - const predicate = vi.fn(isBoolean) as unknown as typeof isBoolean; - - const fn = maybeArray(predicate); - - expect(fn([true])).toBeTruthy(); - expect(fn([false])).toBeTruthy(); - expect(fn(true)).toBeTruthy(); - expect(predicate).toHaveBeenCalledTimes(3); - }); -}); - -describe('maybeNull()', () => { - it('Returns a type predicate function', () => { - expect(maybeNull(isBoolean)).toBeInstanceOf(Function); - }); - - it('Calls a type predicate function', () => { - const predicate = vi.fn(isBoolean) as unknown as typeof isBoolean; - - const fn = maybeNull(predicate); - - expect(fn(true)).toBeTruthy(); - expect(fn(null)).toBeTruthy(); - expect(fn(false)).toBeTruthy(); - expect(predicate).toHaveBeenCalledTimes(2); - }); -}); - -describe('maybeUndefined()', () => { - it('Returns a type predicate function', () => { - expect(maybeUndefined(isBoolean)).toBeInstanceOf(Function); - }); - - it('Calls a type predicate function', () => { - const predicate = vi.fn(isBoolean) as unknown as typeof isBoolean; - - const fn = maybeUndefined(predicate); - - expect(fn(true)).toBeTruthy(); - expect(fn(undefined)).toBeTruthy(); - expect(fn(false)).toBeTruthy(); - expect(predicate).toHaveBeenCalledTimes(2); - }); -}); diff --git a/tsconfig.test.json b/tsconfig.test.json index f8cf741..6c8e1fd 100644 --- a/tsconfig.test.json +++ b/tsconfig.test.json @@ -5,5 +5,5 @@ "verbatimModuleSyntax": true, "tsBuildInfoFile": "./cache/test.tsbuildinfo" }, - "include": ["src/**/*", "test/**/*"] + "include": ["src/**/*", "test/**/*", "bench/**/*"] }