diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..0828ab7 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v18 \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 3662b37..82d4201 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,8 @@ { - "typescript.tsdk": "node_modules/typescript/lib" + "typescript.tsdk": "node_modules/typescript/lib", + "typescript.preferences.importModuleSpecifierEnding": "js", + "explorer.fileNesting.patterns": { + "*.ts": "${capture}.test.ts,${capture}.d.ts,${capture}.js", + "*.js": "${capture}.config.js,${capture}.d.ts", + } } \ No newline at end of file diff --git a/.vscode/typescript.code-snippets b/.vscode/typescript.code-snippets new file mode 100644 index 0000000..e48f7dd --- /dev/null +++ b/.vscode/typescript.code-snippets @@ -0,0 +1,19 @@ +{ + "doc": { + "prefix": "doc", + "body": [ + "/**", + " * $0", + " *", + " * @example", + " *", + " * ```ts", + " * import { $2 } from 'tiinvo'", + " * ```", + " *", + " * @since $1", + " */", + ], + "description": "A shorthand to create jsdoc" + } +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 03988c6..1e8b8e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,19 +1,26 @@ { "name": "tiinvo", - "version": "3.8.1", + "version": "4.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "tiinvo", - "version": "3.8.1", + "version": "4.0.0", "license": "MIT", "devDependencies": { "@types/node": "^17.0.5", + "@vitest/coverage-c8": "^0.24.5", "typescript": "^4.1.5", "vitest": "^0.23.4" } }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, "node_modules/@esbuild/android-arm": { "version": "0.15.10", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.10.tgz", @@ -30,6 +37,40 @@ "node": ">=12" } }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", + "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, "node_modules/@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -56,12 +97,83 @@ "@types/chai": "*" } }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true + }, "node_modules/@types/node": { "version": "17.0.5", "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.5.tgz", "integrity": "sha512-w3mrvNXLeDYV1GKTZorGJQivK6XLCoGwpnyJFbJVK/aTBQUxOCaa/GlFAAN3OTDFcb7h5tiFG+YXCO2By+riZw==", "dev": true }, + "node_modules/@vitest/coverage-c8": { + "version": "0.24.5", + "resolved": "https://registry.npmjs.org/@vitest/coverage-c8/-/coverage-c8-0.24.5.tgz", + "integrity": "sha512-955yK/SdSBZPYrSXgXB0F+0JnOX5EY9kSL7ywJ4rNajmkFUhwLjuKm13Xb6YKSyIY/g5WvbBnyowqfNRxBJ3ww==", + "dev": true, + "dependencies": { + "c8": "^7.12.0", + "vitest": "0.24.5" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vitest/coverage-c8/node_modules/vitest": { + "version": "0.24.5", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.24.5.tgz", + "integrity": "sha512-zw6JhPUHtLILQDe5Q39b/SzoITkG+R7hcFjuthp4xsi6zpmfQPOZcHodZ+3bqoWl4EdGK/p1fuMiEwdxgbGLOA==", + "dev": true, + "dependencies": { + "@types/chai": "^4.3.3", + "@types/chai-subset": "^1.3.3", + "@types/node": "*", + "chai": "^4.3.6", + "debug": "^4.3.4", + "local-pkg": "^0.4.2", + "strip-literal": "^0.4.2", + "tinybench": "^2.3.1", + "tinypool": "^0.3.0", + "tinyspy": "^1.0.2", + "vite": "^3.0.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": ">=v14.16.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@vitest/browser": "*", + "@vitest/ui": "*", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, "node_modules/abab": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", @@ -133,6 +245,30 @@ "node": ">= 6.0.0" } }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", @@ -150,6 +286,22 @@ "optional": true, "peer": true }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/browser-process-hrtime": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", @@ -158,6 +310,32 @@ "optional": true, "peer": true }, + "node_modules/c8": { + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/c8/-/c8-7.12.0.tgz", + "integrity": "sha512-CtgQrHOkyxr5koX1wEUmN/5cfDa2ckbHRA4Gy5LAL0zaCFtVWJS5++n+w4/sr2GWGerBxgTjpKeDclk/Qk6W/A==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@istanbuljs/schema": "^0.1.3", + "find-up": "^5.0.0", + "foreground-child": "^2.0.0", + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-reports": "^3.1.4", + "rimraf": "^3.0.2", + "test-exclude": "^6.0.0", + "v8-to-istanbul": "^9.0.0", + "yargs": "^16.2.0", + "yargs-parser": "^20.2.9" + }, + "bin": { + "c8": "bin/c8.js" + }, + "engines": { + "node": ">=10.12.0" + } + }, "node_modules/chai": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.6.tgz", @@ -185,6 +363,35 @@ "node": "*" } }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -199,6 +406,32 @@ "node": ">= 0.8" } }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/cssom": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", @@ -367,6 +600,21 @@ "node": ">=8" } }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/escodegen": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", @@ -495,6 +743,35 @@ "optional": true, "peer": true }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/foreground-child": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/form-data": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", @@ -511,6 +788,12 @@ "node": ">= 6" } }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, "node_modules/fsevents": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", @@ -531,6 +814,15 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, "node_modules/get-func-name": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", @@ -540,6 +832,26 @@ "node": "*" } }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -552,6 +864,15 @@ "node": ">= 0.4.0" } }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/html-encoding-sniffer": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", @@ -566,6 +887,12 @@ "node": ">=10" } }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, "node_modules/http-proxy-agent": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", @@ -611,6 +938,22 @@ "node": ">=0.10.0" } }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, "node_modules/is-core-module": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", @@ -623,6 +966,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", @@ -631,6 +983,48 @@ "optional": true, "peer": true }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", + "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/jsdom": { "version": "16.7.0", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", @@ -732,6 +1126,21 @@ "url": "https://github.com/sponsors/antfu" } }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -749,6 +1158,21 @@ "get-func-name": "^2.0.0" } }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/mime-db": { "version": "1.51.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", @@ -774,6 +1198,18 @@ "node": ">= 0.6" } }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -800,6 +1236,45 @@ "optional": true, "peer": true }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/parse5": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", @@ -808,6 +1283,33 @@ "optional": true, "peer": true }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", @@ -872,6 +1374,15 @@ "node": ">=6" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", @@ -889,6 +1400,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/rollup": { "version": "2.78.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.78.1.tgz", @@ -926,6 +1452,42 @@ "node": ">=10" } }, + "node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -946,6 +1508,32 @@ "node": ">=0.10.0" } }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-literal": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-0.4.2.tgz", @@ -958,6 +1546,18 @@ "url": "https://github.com/sponsors/antfu" } }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -978,10 +1578,24 @@ "optional": true, "peer": true }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/tinybench": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.2.1.tgz", - "integrity": "sha512-VxB1P8DUhpCC1j2WtKgFYpv3SwU7vtnfmG29cK7hXcqyD7lLiq6SYCVpDceoAT99mvTN+V8Ay4OdtZQbB72+Sw==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.3.1.tgz", + "integrity": "sha512-hGYWYBMPr7p4g5IarQE7XhlyWveh1EKhy4wUBS1LrHXCKYgvz+4/jCqgmJqZxxldesn05vccrtME2RLLZNW7iA==", "dev": true }, "node_modules/tinypool": { @@ -1048,7 +1662,21 @@ "optional": true, "peer": true, "engines": { - "node": ">= 4.0.0" + "node": ">= 4.0.0" + } + }, + "node_modules/v8-to-istanbul": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz", + "integrity": "sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0" + }, + "engines": { + "node": ">=10.12.0" } }, "node_modules/vite": { @@ -1561,6 +2189,21 @@ "optional": true, "peer": true }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", @@ -1572,6 +2215,29 @@ "node": ">=0.10.0" } }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, "node_modules/ws": { "version": "7.5.6", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.6.tgz", @@ -1610,9 +2276,63 @@ "dev": true, "optional": true, "peer": true + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } }, "dependencies": { + "@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, "@esbuild/android-arm": { "version": "0.15.10", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.10.tgz", @@ -1620,6 +2340,34 @@ "dev": true, "optional": true }, + "@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true + }, + "@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", + "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, "@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -1643,12 +2391,49 @@ "@types/chai": "*" } }, + "@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true + }, "@types/node": { "version": "17.0.5", "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.5.tgz", "integrity": "sha512-w3mrvNXLeDYV1GKTZorGJQivK6XLCoGwpnyJFbJVK/aTBQUxOCaa/GlFAAN3OTDFcb7h5tiFG+YXCO2By+riZw==", "dev": true }, + "@vitest/coverage-c8": { + "version": "0.24.5", + "resolved": "https://registry.npmjs.org/@vitest/coverage-c8/-/coverage-c8-0.24.5.tgz", + "integrity": "sha512-955yK/SdSBZPYrSXgXB0F+0JnOX5EY9kSL7ywJ4rNajmkFUhwLjuKm13Xb6YKSyIY/g5WvbBnyowqfNRxBJ3ww==", + "dev": true, + "requires": { + "c8": "^7.12.0", + "vitest": "0.24.5" + }, + "dependencies": { + "vitest": { + "version": "0.24.5", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.24.5.tgz", + "integrity": "sha512-zw6JhPUHtLILQDe5Q39b/SzoITkG+R7hcFjuthp4xsi6zpmfQPOZcHodZ+3bqoWl4EdGK/p1fuMiEwdxgbGLOA==", + "dev": true, + "requires": { + "@types/chai": "^4.3.3", + "@types/chai-subset": "^1.3.3", + "@types/node": "*", + "chai": "^4.3.6", + "debug": "^4.3.4", + "local-pkg": "^0.4.2", + "strip-literal": "^0.4.2", + "tinybench": "^2.3.1", + "tinypool": "^0.3.0", + "tinyspy": "^1.0.2", + "vite": "^3.0.0" + } + } + } + }, "abab": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", @@ -1704,6 +2489,21 @@ "debug": "4" } }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, "assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", @@ -1718,6 +2518,22 @@ "optional": true, "peer": true }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "browser-process-hrtime": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", @@ -1726,6 +2542,26 @@ "optional": true, "peer": true }, + "c8": { + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/c8/-/c8-7.12.0.tgz", + "integrity": "sha512-CtgQrHOkyxr5koX1wEUmN/5cfDa2ckbHRA4Gy5LAL0zaCFtVWJS5++n+w4/sr2GWGerBxgTjpKeDclk/Qk6W/A==", + "dev": true, + "requires": { + "@bcoe/v8-coverage": "^0.2.3", + "@istanbuljs/schema": "^0.1.3", + "find-up": "^5.0.0", + "foreground-child": "^2.0.0", + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-reports": "^3.1.4", + "rimraf": "^3.0.2", + "test-exclude": "^6.0.0", + "v8-to-istanbul": "^9.0.0", + "yargs": "^16.2.0", + "yargs-parser": "^20.2.9" + } + }, "chai": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.6.tgz", @@ -1747,6 +2583,32 @@ "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", "dev": true }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -1758,6 +2620,29 @@ "delayed-stream": "~1.0.0" } }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, "cssom": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", @@ -1897,6 +2782,18 @@ } } }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, "escodegen": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", @@ -1993,6 +2890,26 @@ "optional": true, "peer": true }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "foreground-child": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" + } + }, "form-data": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", @@ -2006,6 +2923,12 @@ "mime-types": "^2.1.12" } }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, "fsevents": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", @@ -2019,12 +2942,32 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, "get-func-name": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", "dev": true }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -2034,6 +2977,12 @@ "function-bind": "^1.1.1" } }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, "html-encoding-sniffer": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", @@ -2045,6 +2994,12 @@ "whatwg-encoding": "^1.0.5" } }, + "html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, "http-proxy-agent": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", @@ -2081,6 +3036,22 @@ "safer-buffer": ">= 2.1.2 < 3" } }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, "is-core-module": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", @@ -2090,6 +3061,12 @@ "has": "^1.0.3" } }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, "is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", @@ -2098,6 +3075,39 @@ "optional": true, "peer": true }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true + }, + "istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + } + }, + "istanbul-reports": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", + "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "dev": true, + "requires": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + } + }, "jsdom": { "version": "16.7.0", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", @@ -2175,6 +3185,15 @@ "integrity": "sha512-mlERgSPrbxU3BP4qBqAvvwlgW4MTg78iwJdGGnv7kibKjWcJksrG3t6LB5lXI93wXRDvG4NpUgJFmTG4T6rdrg==", "dev": true }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -2192,6 +3211,15 @@ "get-func-name": "^2.0.0" } }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "requires": { + "semver": "^6.0.0" + } + }, "mime-db": { "version": "1.51.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", @@ -2211,6 +3239,15 @@ "mime-db": "1.51.0" } }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -2231,6 +3268,33 @@ "optional": true, "peer": true }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, "parse5": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", @@ -2239,6 +3303,24 @@ "optional": true, "peer": true }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, "path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", @@ -2284,6 +3366,12 @@ "optional": true, "peer": true }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true + }, "resolve": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", @@ -2295,6 +3383,15 @@ "supports-preserve-symlinks-flag": "^1.0.0" } }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, "rollup": { "version": "2.78.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.78.1.tgz", @@ -2323,6 +3420,33 @@ "xmlchars": "^2.2.0" } }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -2337,6 +3461,26 @@ "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", "dev": true }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, "strip-literal": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-0.4.2.tgz", @@ -2346,6 +3490,15 @@ "acorn": "^8.8.0" } }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, "supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -2360,10 +3513,21 @@ "optional": true, "peer": true }, + "test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "requires": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + } + }, "tinybench": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.2.1.tgz", - "integrity": "sha512-VxB1P8DUhpCC1j2WtKgFYpv3SwU7vtnfmG29cK7hXcqyD7lLiq6SYCVpDceoAT99mvTN+V8Ay4OdtZQbB72+Sw==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.3.1.tgz", + "integrity": "sha512-hGYWYBMPr7p4g5IarQE7XhlyWveh1EKhy4wUBS1LrHXCKYgvz+4/jCqgmJqZxxldesn05vccrtME2RLLZNW7iA==", "dev": true }, "tinypool": { @@ -2411,6 +3575,17 @@ "optional": true, "peer": true }, + "v8-to-istanbul": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz", + "integrity": "sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0" + } + }, "vite": { "version": "3.1.4", "resolved": "https://registry.npmjs.org/vite/-/vite-3.1.4.tgz", @@ -2663,6 +3838,15 @@ "optional": true, "peer": true }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, "word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", @@ -2671,6 +3855,23 @@ "optional": true, "peer": true }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, "ws": { "version": "7.5.6", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.6.tgz", @@ -2695,6 +3896,39 @@ "dev": true, "optional": true, "peer": true + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true } } } diff --git a/package.json b/package.json index 8dc36e9..e8ebcb9 100644 --- a/package.json +++ b/package.json @@ -1,32 +1,23 @@ { "name": "tiinvo", - "version": "3.9.0", + "version": "4.0.0", "author": "Paolo Roth ", "license": "MIT", "description": "A lib of types and utilities for your TypeScript and JavaScript projects", "types": "./index.d.ts", + "type": "module", "main": "./index.js", "module": "./index.mjs", - "exports": { - ".": { - "require": "./index.js", - "import": "./index.mjs" - }, - "./*": { - "require": "./*.js", - "import": "./*.mjs" - } - }, "keywords": [ "functional programming", "functions", "utility" ], "scripts": { - "build": "tsc -p . && tsc -p ./tsconfig.commonjs.json && node scripts/rename.js && node scripts/copy.js", + "build": "tsc -p .", "changelog": "npx standard-changelog --first-release", - "clean": "tsc --build --clean && node scripts/clean.js", - "test": "vitest run", + "clean": "tsc --build --clean", + "test": "vitest run --coverage", "build:docs": "npm --prefix docs run build", "dev:docs": "npm --prefix docs run dev", "deploy": "npm test && npm run clean && npm run build && cd dist && npm publish && cd .. && npm run clean", @@ -39,7 +30,8 @@ }, "devDependencies": { "@types/node": "^17.0.5", + "@vitest/coverage-c8": "^0.24.5", "typescript": "^4.1.5", "vitest": "^0.23.4" } -} \ No newline at end of file +} diff --git a/scripts/clean.js b/scripts/clean.js deleted file mode 100644 index 46fbcf1..0000000 --- a/scripts/clean.js +++ /dev/null @@ -1,22 +0,0 @@ -const fs = require('fs') -const path = require('path') - -const basedir = __dirname -const distdir = path.join(basedir, '..', 'dist') -const srcdir = path.join(basedir, '..', 'src') -const distdirfilenames = fs.readdirSync(distdir) -const srcfilenames = fs.readdirSync(srcdir).filter(a => a.endsWith('.mjs')); - -const removefilebyname = basedir => filename => { - let filepath = path.join(basedir, filename); - let stat = fs.statSync(filepath); - - if (stat.isDirectory()) { - fs.readdirSync(filepath).forEach(removefilebyname(filepath)); - } else if (stat.isFile()) { - fs.unlinkSync(filepath); - } -} - -srcfilenames.forEach(removefilebyname(srcdir)); -distdirfilenames.forEach(removefilebyname(distdir)); diff --git a/scripts/copy.js b/scripts/copy.js deleted file mode 100644 index 0b2376d..0000000 --- a/scripts/copy.js +++ /dev/null @@ -1,22 +0,0 @@ -const path = require(`path`) -const fs = require(`fs`) - -const filestoinclude = [ - `.gitignore`, - `.npmignore`, - `banner.png`, - // `LICENCE`, - `Logo.svg`, - `package.json`, - `README.md`, -] - -const rootdir = path.join(__dirname, '..'); -const distdir = path.join(rootdir, 'dist'); - -filestoinclude.forEach(filetoinclude => { - const filepath = path.join(rootdir, filetoinclude); - const destpath = path.join(distdir, filetoinclude); - - fs.copyFileSync(filepath, destpath); -}) diff --git a/scripts/rename.js b/scripts/rename.js deleted file mode 100644 index 09a632e..0000000 --- a/scripts/rename.js +++ /dev/null @@ -1,18 +0,0 @@ -const fs = require('fs') -const path = require('path') - -const basedir = __dirname -const srcdir = path.join(basedir, '..', 'src') -const filenames = fs.readdirSync(srcdir).filter(a => a.endsWith('.js')); - -filenames.forEach(filename => { - const filepath = path.join(srcdir, filename); - const newfilepath = filepath.replace('.js', '.mjs').replace(`src`, `dist`); - fs.renameSync(filepath, newfilepath) -}); - -const fp = path.join(basedir, '..', 'dist', 'index.mjs'); -const outindex = fs.readFileSync(fp); -const replaced = outindex.toString().replace(/';/gm, ".mjs';"); - -fs.writeFileSync(fp, replaced); \ No newline at end of file diff --git a/src/Arr.test.ts b/src/Arr.test.ts new file mode 100644 index 0000000..b17cbbc --- /dev/null +++ b/src/Arr.test.ts @@ -0,0 +1,319 @@ +import { describe, expect, test } from 'vitest'; +import * as Arr from './Arr.js'; +import * as Num from './Num.js'; +import * as Functors from './Functors.js'; +import * as Str from './Str.js'; + +describe("Arr", () => { + test(Arr.cmp.name, () => { + const cmp = Arr.cmp(Num.cmp); + + expect(cmp([], [])).toBe(0); + expect(cmp([1], [])).toBe(1); + expect(cmp([1], [1])).toBe(0); + expect(cmp([0], [1])).toBe(-1); + expect(cmp([0, 1], [1, 0])).toBe(0); + expect(cmp([1, 0], [0, 1])).toBe(0); + expect(cmp([0, 1, 1], [1, 0])).toBe(1); + expect(cmp([1, 0], [0, 1, 1])).toBe(-1); + + const cmp2 = Arr.cmp(Str.cmp); + + expect(cmp2(['a'], ['a'])).toEqual(0); + expect(cmp2(['a'], ['b'])).toEqual(-1); + expect(cmp2(['b'], ['a'])).toEqual(1); + expect(cmp2(['a'], ['a', 'b'])).toEqual(-1); + expect(cmp2(['a', 'b'], ['a'])).toEqual(1); + expect(cmp2(['a', 'b'], ['a', 'b'])).toEqual(0); + expect(cmp2(['a', 'b', 'c'], ['a', 'b'])).toEqual(1); + expect(cmp2(['a', 'b', 'c'], ['a', 'b', 'c'])).toEqual(0); + }); + + test(Arr.concat.name, () => { + expect(Arr.concat([], [])).toEqual([]); + expect(Arr.concat([1], [2])).toEqual([1, 2]); + expect(Arr.concat([1])([2])).toEqual([2, 1]); + }); + + test(Arr.contains.name, () => { + expect(Arr.contains(['a'], 'a')).toEqual(true); + expect(Arr.contains(['a'], 'b')).toEqual(false); + expect(Arr.contains('a')(['a'])).toEqual(true); + expect(Arr.contains('a')(['b'])).toEqual(false); + }); + + test(Arr.empty.name, () => { + expect(Arr.empty([])).toEqual(true); + expect(Arr.empty(['a'])).toEqual(false); + }); + + test(Arr.eq.name, () => { + const eq = Arr.eq(Str.eq); + + expect(eq(['a'], ['a'])).toEqual(true); + expect(eq(['a'], ['b'])).toEqual(false); + expect(eq(['b'], ['a'])).toEqual(false); + expect(eq(['a'], ['a', 'b'])).toEqual(false); + expect(eq(['a', 'b'], ['a'])).toEqual(false); + expect(eq(['a', 'b'], ['b', 'a'])).toEqual(false); + expect(eq(['a', 'b'], ['a', 'b'])).toEqual(true); + }); + + test(Arr.every.name, () => { + expect(Arr.every([10, 20], Num.isEven)).toEqual(true); + expect(Arr.every([10, 21], Num.isEven)).toEqual(false); + expect(Arr.every(Num.isEven)([10, 20])).toEqual(true); + expect(Arr.every(Num.isEven)([10, 21])).toEqual(false); + }); + + test(Arr.fill.name, () => { + const x = Arr.make(4); + + Arr.fill(x, 10, 0, 3); // [10, 10, 10, 10] + Arr.fill(10, 0, 3)(x); // [10, 10, 10, 10] + Arr.fill(x, 10); // [10, 10, 10, 10] + Arr.fill(10)(x); // [10, 10, 10, 10] + }); + + test(Arr.filter.name, () => { + const x = [10, 20, 30]; + + expect(Arr.filter(x, Num.gt(10))).toEqual([20, 30]); + expect(Arr.filter(Num.gt(10))(x)).toEqual([20, 30]); + }); + + test(Arr.filterMap.name, () => { + const x = [-10, 10]; + const mod: Functors.FilterMappableModule = { + filter: Num.gt(0), + map: Num.add(10), + }; + + expect(Arr.filterMap(x, mod)).toEqual([20]); + expect(Arr.filterMap(mod)(x)).toEqual([20]); + expect(() => Arr.filterMap({} as any)(x)).toThrow(); + expect(() => Arr.filterMap(x, {} as any)).toThrow(); + }); + + test(Arr.filterReduce.name, () => { + const mod: Functors.FilterReduceableModule = { + [Functors.defaultsymbol]: 0, + filter: Num.isPositive, + reduce: Num.add, + }; + + const x = [-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5]; + + expect(Arr.filterReduce(x, mod)).toEqual(15); + expect(Arr.filterReduce(mod)(x)).toEqual(15); + expect(() => Arr.filterReduce({} as any)(x)).toThrow(); + expect(() => Arr.filterReduce(x, {} as any)).toThrow(); + }); + + test(Arr.find.name, () => { + const x = [10, 20, 30]; + const gt10 = Num.gt(10); + const gt30 = Num.gt(30); + + expect(Arr.find(x, gt10)).toEqual(20); + expect(Arr.find(x, gt30)).toEqual(null); + expect(Arr.find(gt10)(x)).toEqual(20); + expect(Arr.find(gt30)(x)).toEqual(null); + }); + + test(Arr.first.name, () => { + expect(Arr.first(['a', 'b'])).toEqual('a'); + expect(Arr.first([])).toEqual(null); + }); + + test(Arr.firstOr.name, () => { + expect(Arr.firstOr([10, 20], 0)).toEqual(10); + expect(Arr.firstOr(0)([10, 20])).toEqual(10); + expect(Arr.firstOr([], 0)).toEqual(0); + expect(Arr.firstOr(0)([])).toEqual(0); + }); + + test(Arr.flat.name, () => { + const x = [[10, 20], [['hello', 'world']]]; + + expect(Arr.flat(x)).toEqual([10, 20, ['hello', 'world']]); + expect(Arr.flat(x, 2)).toEqual([10, 20, 'hello', 'world']); + expect(Arr.flat()(x)).toEqual([10, 20, ['hello', 'world']]); + expect(Arr.flat(2)(x)).toEqual([10, 20, 'hello', 'world']); + }); + + test(Arr.flatMap.name, () => { + const x = [[2, 3], [4, 5]]; + + expect(Arr.flatMap(x, Num.add(1))).toEqual([3, 4, 5, 6]); + expect(Arr.flatMap(Num.add(1))(x)).toEqual([3, 4, 5, 6]); + }); + + test(Arr.from.name, () => { + expect(Arr.from([1, 2, 3])).toEqual([1, 2, 3]); + expect(Arr.from(new Set([1, 2, 3]))).toEqual([1, 2, 3]); + }); + + test(Arr.get.name, () => { + const err = Error("Index out of bounds 3 for length 2"); + + expect(Arr.get([10, 20], 1)).toEqual(20); + expect(Arr.get([10, 20], 3)).toEqual(err); + expect(Arr.get(1)([10, 20])).toEqual(20); + expect(Arr.get(3)([10, 20])).toEqual(err); + }); + + test(Arr.guard.name, () => { + expect(Arr.guard([])).toEqual(true); + expect(Arr.guard(null)).toEqual(false); + expect(Arr.guard(undefined)).toEqual(false); + expect(Arr.guard(0)).toEqual(false); + expect(Arr.guard('')).toEqual(false); + }); + + test(Arr.guardOf.name, () => { + const isStrArr = Arr.guardOf(Str.guard); + + expect(isStrArr(10)).toEqual(false); + expect(isStrArr([])).toEqual(true); + expect(isStrArr(['a'])).toEqual(true); + expect(isStrArr(['a', 'b'])).toEqual(true); + expect(isStrArr(['a', 'b', 'c'])).toEqual(true); + expect(isStrArr(['a', 'b', 'c', 1])).toEqual(false); + + expect(Arr.guardOf(Str.guard, 10)).toEqual(false); + expect(Arr.guardOf(Str.guard, [])).toEqual(true); + expect(Arr.guardOf(Str.guard, ['a'])).toEqual(true); + expect(Arr.guardOf(Str.guard, ['a', 'b'])).toEqual(true); + expect(Arr.guardOf(Str.guard, ['a', 'b', 'c'])).toEqual(true); + expect(Arr.guardOf(Str.guard, ['a', 'b', 'c', 1])).toEqual(false); + }); + + test(Arr.includes.name, () => { + const x = [10, 20, 30]; + + expect(Arr.includes(x, 30)).toEqual(true); + expect(Arr.includes(30)(x)).toEqual(true); + expect(Arr.includes(x, 40)).toEqual(false); + expect(Arr.includes(40)(x)).toEqual(false); + }); + + test(Arr.join.name, () => { + const x = [10, 20, 30]; + + expect(Arr.join(x, '-')).toEqual('10-20-30'); + expect(Arr.join('-')(x)).toEqual('10-20-30'); + expect(Arr.join(x)).toEqual('102030'); + expect(Arr.join()(x)).toEqual('102030'); + }); + + test(Arr.last.name, () => { + expect(Arr.last(['a', 'b'])).toEqual('b'); + expect(Arr.last([])).toEqual(null); + }); + + test(Arr.lastOr.name, () => { + expect(Arr.lastOr([10, 20], 0)).toEqual(20); + expect(Arr.lastOr(0)([10, 20])).toEqual(20); + expect(Arr.lastOr([], 0)).toEqual(0); + expect(Arr.lastOr(0)([])).toEqual(0); + }); + + test(Arr.length.name, () => { + expect(Arr.length([])).toEqual(0); + expect(Arr.length([1])).toEqual(1); + expect(Arr.length([1, 2, 3])).toEqual(3); + }); + + test(Arr.make.name, () => { + expect(Arr.make(3)).toEqual([undefined, undefined, undefined]); + expect(Arr.make(3, 'hello')).toEqual(['hello', 'hello', 'hello']); + expect(Arr.make(3, x => ((x + 1) * 10).toString(16))).toEqual(['a', '14', '1e']); + }); + + test(Arr.map.name, () => { + const x = [1, 2, 3]; + const m = Num.mul(2); + + expect(Arr.map(x, m)).toEqual([2, 4, 6]); + expect(Arr.map(m)(x)).toEqual([2, 4, 6]); + }); + + test(Arr.none.name, () => { + const x = [1, 3, 5]; + const p = Num.isEven; + + expect(Arr.none(x, p)).toEqual(true); + expect(Arr.none(p)(x)).toEqual(true); + }); + + test(Arr.of.name, () => { + expect(Arr.of(1, 2, 3)).toEqual([1, 2, 3]); + }); + + test(Arr.populated.name, () => { + expect(Arr.populated([])).toEqual(false); + expect(Arr.populated(['a'])).toEqual(true); + }); + + test(Arr.random.name, () => { + expect(Arr.random(['a', 'b', 'c'])).oneOf(['a', 'b', 'c']); + }); + + test(Arr.reduce.name, () => { + const x = [1, 2, 3, 4, 5]; + + Arr.reduce(x, Num.add, 0); // 15 + Arr.reduce(Num.add, 0)(x); // 15 + }); + + test(Arr.reduceRight.name, () => { + const x = [1, 2, 3, 4, 5]; + + expect(Arr.reduceRight(x, Num.sub, 0)).toEqual(-15); + expect(Arr.reduceRight(Num.sub, 0)(x)).toEqual(-15); + }); + + test(Arr.reverse.name, () => { + expect(Arr.reverse([10, 20, 30])).toEqual([30, 20, 10]); + }); + + test(Arr.slice.name, () => { + const x = [10, 20, 30]; + + expect(Arr.slice(x)).toEqual([10, 20, 30]); + expect(Arr.slice(x, 2)).toEqual([30]); + expect(Arr.slice(x, 1, 2)).toEqual([20]); + expect(Arr.slice()(x)).toEqual([10, 20, 30]); + expect(Arr.slice(2)(x)).toEqual([30]); + expect(Arr.slice(1, 2)(x)).toEqual([20]); + }); + + test(Arr.shuffle.name, () => { + expect(Arr.shuffle([10, 20, 30]).join()).oneOf([ + [10, 20, 30].join(), + [10, 30, 20].join(), + [20, 10, 30].join(), + [20, 30, 10].join(), + [30, 10, 20].join(), + [30, 20, 10].join(), + ]); + }); + + test(Arr.some.name, () => { + const x = [1, 2, 3]; + const p = Num.isEven; + + expect(Arr.some(x, p)).toBe(true); + expect(Arr.some(p)(x)).toBe(true); + }); + + test(Arr.sort.name, () => { + const x = [3, 1, 2, 5, 4]; + const s = Num.asc; + + expect(Arr.sort(x, s)).toEqual([1, 2, 3, 4, 5]); + expect(Arr.sort(s)(x)).toEqual([1, 2, 3, 4, 5]); + }); + +}); diff --git a/src/Arr.ts b/src/Arr.ts new file mode 100644 index 0000000..1731673 --- /dev/null +++ b/src/Arr.ts @@ -0,0 +1,1037 @@ +import type * as Fn from './Fn.js'; +import type * as Functors from './Functors.js'; +import { defaultsymbol } from './Functors.js'; +import type * as Predicate from './Predicate.js'; +import type * as Option from './Option.js'; +import { guard as predicateguard } from './Predicate.js'; +import type * as Result from './Result.js'; + +export type t = a[]; + +export type Reducer = { + (p: b, c: a): b; + (p: b, c: a, i: number): b; + (p: b, c: a, i: number, t: t): b; +}; + +//#region guardables + +/** + * Returns true if `a` is an array. + * + * ```ts + * import { Arr } from 'tiinvo'; + * + * Arr.guard([]) // true + * Arr.guard(null) // false + * Arr.guard(undefined) // false + * Arr.guard(0) // false + * Arr.guard('') // false + * ``` + * + * @since 4.0.0 + */ +export const guard = (x: unknown): x is t => Array.isArray(x); + +/** + * Returns true if `b` is an array of `a`. + * + * ```ts + * import { Arr, Str } from 'tiinvo'; + * + * const isStrArr = Arr.guardOf(Str.guard); + * + * isStrArr([]) // true + * isStrArr(['a']) // true + * isStrArr(['a', 'b']) // true + * isStrArr(['a', 'b', 'c']) // true + * isStrArr(['a', 'b', 'c', 1]) // false + * ```` + * + * @param a + * @returns + * @since 4.0.0 + */ +export function guardOf(g: Functors.Guardable, x: unknown): x is t; +export function guardOf(g: Functors.Guardable): (x: unknown) => x is t; +export function guardOf(g: Functors.Guardable, x?: unknown): any { + const c = (g: Functors.Guardable, x: unknown): x is t => { + if (!guard(x)) { + return false; + } + + for (let i = 0; i < x.length; i++) { + if (!g(x[i])) { + return false; + } + } + + return true; + }; + + if (arguments.length === 2) { + return c(g, x); + } + + return (x: unknown): x is t => c(g, x); +}; + +//#endregion + +//#region comparables + +/** + * Compares two arrays `a[]` with a given `Comparable`. + * + * ```ts + * import { Arr, Str } from 'tiinvo'; + * + * const cmp = Arr.cmp(Str.cmp); + * + * console.log(cmp(['a'], ['a'])) // 0 + * console.log(cmp(['a'], ['b'])) // -1 + * console.log(cmp(['b'], ['a'])) // 1 + * console.log(cmp(['a'], ['a', 'b'])) // -1 + * console.log(cmp(['a', 'b'], ['a'])) // 1 + * console.log(cmp(['a', 'b'], ['a', 'b'])) // 0 + * console.log(cmp(['a', 'b', 'c'], ['a', 'b'])) // 1 + * console.log(cmp(['a', 'b', 'c'], ['a', 'b', 'c'])) // 0 + * ``` + * + * @returns + * @since 4.0.0 + */ +export const cmp = (cmp: Functors.Comparable) => (a: t, b: t): Functors.ComparableResult => { + if (a.length > b.length) { + return 1; + } else if (a.length < b.length) { + return -1; + } + + let s = 0; + + for (let i = 0; i < a.length; i++) { + s += cmp(a[i], b[i]); + } + + if (s === 0) { + return s; + } else if (s >= 1) { + return 1; + } else { + return -1; + } +}; + +/** + * Compares two arrays `a[]` with a given `Equatable` and returns true if are identical. + * + * ```ts + * import { Arr, Str } from 'tiinvo'; + * + * const eq = Arr.eq(Str.eq); + * + * console.log(eq(['a'], ['a'])) // true + * console.log(eq(['a'], ['b'])) // false + * console.log(eq(['b'], ['a'])) // false + * console.log(eq(['a'], ['a', 'b'])) // false + * console.log(eq(['a', 'b'], ['a'])) // false + * console.log(eq(['a', 'b'], ['b', 'a'])) // false + * console.log(eq(['a', 'b'], ['a', 'b'])) // true + * ``` + * + * @returns + * @since 4.0.0 + */ +export const eq = (e: Functors.Equatable): Functors.Equatable> => (a, b) => { + if (a.length !== b.length) { + return false; + } + + let s = 0; + + for (let i = 0; i < a.length; i++) { + s += e(a[i], b[i]) ? 0 : 1; + } + + return s === 0; +};; + +//#endregion + +//#region native methods + +/** + * Concates `b` and `a` without modifying the original arrays. + * + * ```ts + * import { Arr } from 'tiinvo'; + * + * Arr.concat([10], [20]) // [10, 20] + * Arr.concat([10])([20]) // [20, 10] + * ``` + * @param a + * @returns + * @since 4.0.0 + */ +export function concat>(a: a, b: a): a; +export function concat>(a: a): Fn.Unary; +export function concat>(a: any, b?: any): any { + if (guard(b) && guard(a)) { + return a.concat(b); + } + + return (b: a) => b.concat(a); +} + +/** + * Returns `true` if an array `a` contains `b`. + * + * ```ts + * import { Arr } 'tiinvo'; + * + * Arr.contains(['a'], 'a') // true + * Arr.contains(['a'], 'b') // false + * Arr.contains('a')(['a']) // true + * Arr.contains('a')(['b']) // false + * ``` + * + * @param a + * @returns + * @since 4.0.0 + */ +export function contains(a: t, b: a): boolean; +export function contains(b: a): Fn.Unary; +export function contains(a: any, b?: any): any { + if (guard(a)) { + return a.indexOf(b) >= 0; + } + + return (b: t) => guard(b) && b.indexOf(a) >= 0; +} + +/** + * Determines whether all the members of an array `a` satisfy the specified predicate `p`. + * + * ```ts + * import { Arr, Num } 'tiinvo'; + * + * Arr.every([10, 20], Num.isEven) // true + * Arr.every([10, 21], Num.isEven) // false + * Arr.every(Num.isEven)([10, 20]) // true + * Arr.every(Num.isEven)([10, 21]) // false + * ``` + * + * @param p + * @returns + * @since 4.0.0 + */ +export function every(a: t, p: Predicate.t): boolean; +export function every(a: Predicate.t): Fn.Unary, boolean>; +export function every(a: t | Predicate.t, p?: Predicate.t): any { + if (guard(a) && !!p) { + return a.every(p); + } + + return (b: t) => b.every(a as Predicate.t); +} + +/** + * Creates an array from an array-like object. + * + * ```ts + * import { Arr } 'tiinvo'; + * + * Arr.from([1, 2, 3]) // [1, 2, 3] + * Arr.from(new Set([1, 2, 3])) // [1, 2, 3] + * ``` + * + * @param a + * @returns + * @since 4.0.0 + */ +export const from = Array.from; + +/** + * Returns the element `Result.t` at index `i` of an array `a[]`. + * + * If the index `i` is out of range, a `Err` will be returned. + * + * ```ts + * import { Arr } 'tiinvo'; + * + * Arr.get([10, 20], 1) // 20 + * Arr.get([10, 20], 3) // Error("Index out of bounds 3 for length 2") + * Arr.get(1)([10, 20]) // 20 + * Arr.get(3)([10, 20]) // Error("Index out of bounds 3 for length 2") + * ``` + * + * @param i + * @returns + * @since 4.0.0 + */ +export function get(a: t, i: number): Result.t; +export function get(a: number): Fn.Unary, Result.t>; +export function get(a: t | number, i?: number): any { + if (guard(a) && typeof i === 'number') { + if (i >= 0 && i < a.length) { + return a[i] as Result.t; + } + + return new RangeError(`Index out of bounds ${i} for length ${a.length}`); + } + + return (b: t) => { + if (a >= 0 && a < b.length) { + return b[a as number] as Result.t; + } + + return new RangeError(`Index out of bounds ${a} for length ${b.length}`); + }; +}; + +/** + * Fills an array `a[]` with `a` from index `start` to `end`. + * This do not modify the original array. + * + * ```ts + * import { Arr } from 'tiinvo'; + * + * const x = Arr.make(4) + * + * Arr.fill(x, 10, 0, 3) // [10, 10, 10, 10] + * Arr.fill(10, 0, 3)(x) // [10, 10, 10, 10] + * Arr.fill(x, 10) // [10, 10, 10, 10] + * Arr.fill(10)(x) // [10, 10, 10, 10] + * + * ``` + * + * @param a + * @param start + * @param end + * @returns + * @since 4.0.0 + */ +export function fill(a: t, b: a, start?: number, end?: number): t; +export function fill(a: a, b?: number, start?: number): (a: t, start2?: number, end2?: number) => t; +export function fill(a: t | a, b?: any, start?: any, end?: any): any { + if (guard(a)) { + return a.fill(b, start, end); + } + + return (c: t, start2?: number, end2?: number) => c.fill(b, b ?? start2, start ?? end2); +} + +/** + * Returns the elements of an array `a` that meet the condition specified in a predicate `p`. + * + * ```ts + * import { Arr, Num } 'tiinvo'; + * + * const x = [10, 20, 30]; + * + * Arr.filter(x, Num.gt(10)) // [20, 30] + * Arr.filter(Num.gt(10))(x) // [20, 30] + * ``` + * + * @param p + * @returns + * @since 4.0.0 + */ +export function filter(a: t, p: Predicate.t): t; +export function filter(a: Predicate.t): Fn.Unary, t>; +export function filter(a: t | Predicate.t, p?: Predicate.t): any { + if (guard(a) && predicateguard(p)) { + return a.filter(p); + } + + return (b: t) => b.filter(a as Predicate.t); +} + +/** + * Finds the first value `a` with a given predicate `p` and returns `Option.some` if found, otherwise returns `Option.none`. + * + * ```ts + * import { Arr, Num } 'tiinvo'; + * + * const x = [10, 20, 30]; + * const gt10 = Num.gt(10); + * const gt30 = Num.gt(30); + * + * Arr.find(x, gt10) // 20 + * Arr.find(x, gt30) // null + * Arr.find(gt10)(x) // 20 + * Arr.find(gt30)(x) // null + * ``` + * + * @param p + * @returns + * @since 4.0.0 + */ +export function find(a: t, p: Predicate.t): Option.t; +export function find(a: Predicate.t): Fn.Unary, Option.t>; +export function find(a: t | Predicate.t, p?: Predicate.t): any { + if (guard(a) && predicateguard(p)) { + return a.find(p) ?? null; + } + + return (b: t) => b.find(a as Predicate.t) ?? null; +} + +/** + * Returns the first element of an array `a`. If the array is empty, returns `none`. + * + * @example + * + * ```ts + * import { Arr } 'tiinvo'; + * + * Arr.first(['a', 'b']) // 'a'; + * Arr.first([]) // null; + * ``` + * + * @returns + * @since 4.0.0 + */ +export const first = (t: t): Option.t => t[0] ?? null as Option.t; + +/** + * Returns the first element of an array `a` or `b` if the array is empty. + * + * @example + * + * ```ts + * import { Arr } from 'tiinvo'; + * + * Arr.firstOr([10, 20], 0) // 10 + * Arr.firstOr(0)([10, 20]) // 10 + * Arr.firstOr([], 0) // 0 + * Arr.firstOr(0)([]) // 0 + * ``` + * + * @since 4.0.0 + */ +export function firstOr(t: t, b: a): Option.t; +export function firstOr(t: a): Fn.Unary, Option.t>; +export function firstOr(t: t | a, b?: any): any { + if (guard(t)) { + return t[0] ?? b; + } + + return (b: t) => b[0] ?? t; +} + +/** + * Maps with the `mod.map` function an array `a[]` by removing all elements that do not satisfy the predicate `mod.filter`. + * The filter occurs before mapping the elements. + * + * @example + * + * ```ts + * import { Arr, Functors, Num } from 'tiinvo'; + * + * const x = [-10, 10] + * const mod: Functors.FilterMappableModule = { + * filter: Num.gt(0), + * map: Num.add(10), + * }; + * + * Arr.filterMap(x, mod) // [20] + * Arr.filterMap(mod)(x) // [20] + * ``` + * + * @since 4.0.0 + */ +export function filterMap(a: t, mod: Functors.FilterMappableModule): t; +export function filterMap(a: Functors.FilterMappableModule): Fn.Unary, t>; +export function filterMap(a: t | Functors.FilterMappableModule, mod?: Functors.FilterMappableModule): any { + const gmod = (x: unknown): x is Functors.FilterMappableModule => typeof x === 'object' && x !== null && 'filter' in x && 'map' in x + + if (guard(a) && gmod(mod)) { + const r = [] as b[]; + + for (let i = 0; i < a.length; i++) { + const v = a[i]; + + if (mod.filter(v)) { + r.push(mod.map(v)); + } + } + + return r; + } else if (gmod(a) && !Array.isArray(a)) { + return (b: t) => { + const r = [] as b[]; + + for (let i = 0; i < b.length; i++) { + if (a.filter(b[i])) { + r.push(a.map(b[i])); + } + } + + return r; + }; + } + + throw new Error('Invalid functor passed, required Functors.FilterReduceable'); +} + +/** + * Like a normal array reduce, but after a filter has been applied on each iteration. + * + * If the filter is satisfied, then the reduce occurs for the current element, otherwise it skips. + * + * @example + * + * ```ts + * import { Arr, Functors, Num } from 'tiinvo' + * + * const mod: Functors.FilterReduceableModule = { + * [Functors.defaultsymbol]: 0, + * filter: Num.isPositive, + * reduce: Num.add, + * } + * + * const x = [-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5] + * + * Arr.filterReduce(x, mod) // 15 + * Arr.filterReduce(mod)(x) // 15 + * ``` + * + * @since 4.0.0 + */ +export function filterReduce(a: t, mod: Functors.FilterReduceableModule): b; +export function filterReduce(a: Functors.FilterReduceableModule): Fn.Unary, b>; +export function filterReduce(a: any, mod?: Functors.FilterReduceableModule): any { + if (guard(a) && typeof mod === 'object' && mod !== null && 'filter' in mod && 'reduce' in mod && defaultsymbol in mod) { + let out = mod[defaultsymbol]; + + for (let i = 0; i < a.length; i++) { + if (mod.filter(a[i] as a)) { + out = mod.reduce(out, a[i] as a); + } + } + + return out; + } else if (typeof a === 'object' && a !== null && 'filter' in a && 'reduce' in a && defaultsymbol in a) { + return (b: t) => { + let out = a[defaultsymbol]; + + for (let i = 0; i < b.length; i++) { + if (a.filter(b[i])) { + out = a.reduce(out, b[i]); + } + } + + return out; + }; + } else { + throw new Error('Invalid functor passed, required Functors.FilterReduceable'); + } +} + +/** + * Flatterns an array. + * + * @example + * + * ```ts + * import { Arr } from 'tiinvo'; + * + * const x = [[10, 20], [['hello', 'world']]] + * + * Arr.flat(x) // [10, 20, ['hello', 'world']] + * Arr.flat(x, 2) // [10, 20, 'hello', 'world'] + * Arr.flat()(x) // [10, 20, ['hello', 'world']] + * Arr.flat(2)(x) // [10, 20, 'hello', 'world'] + * ``` + * + * @since 4.0.0 + */ +export function flat, d extends number = 1>(a: a, d?: d): FlatArray; +export function flat, d extends number = 1>(a?: d): Fn.Unary>; +export function flat, d extends number = 1>(a: a | d, d?: d): any { + if (guard(a)) { + return a.flat(d) as FlatArray; + } + + return (b: a) => b.flat(a) as FlatArray; +} + +/** + * Maps a matrix `a[][]` to a `b[]` using the mapping function `m`. + * + * @example + * + * ```ts + * import { Arr, Num } from 'tiinvo'; + * + * const x = [[2, 3], [4, 5]] + * + * Arr.flatMap(x, Num.add(1)) // [3, 4, 5, 6] + * Arr.flatMap(Num.add(1))(x) // [3, 4, 5, 6] + * ``` + * + * @since 4.0.0 + */ +export function flatMap(a: t>, m: Functors.Mappable): t; +export function flatMap(a: Functors.Mappable): (a: t>) => t; +export function flatMap(a: t> | Functors.Mappable, m?: any): any { + if (guard(a) && typeof m === 'function') { + return flat((a as t>).map(b => b.map(m)), 1); + } + + return (b: t>) => flat(b.map(c => c.map(a as Functors.Mappable)), 1); +} + +/** + * Determines whether an array includes a certain element, returning `true` or `false` as appropriate. + * + * @example + * + * ```ts + * import { Arr } from 'tiinvo' + * + * const x = [10, 20, 30] + * + * Arr.includes(x, 30) // true + * Arr.includes(30)(x) // true + * Arr.includes(x, 40) // false + * Arr.includes(40)(x) // false + * ``` + * + * @since 4.0.0 + */ +export function includes(a: t, b: a): boolean; +export function includes(a: a): Fn.Unary, boolean>; +export function includes(a: t | a, b?: a): any { + if (guard(a) && arguments.length === 2) { + return a.includes(b as a); + } + + return (b: t) => b.includes(a as a); +} + +/** + * Returns the last element of an array `a`. If the array is empty, returns `none`. + * + * @example + * + * ```ts + * import { Arr } 'tiinvo'; + * + * Arr.last(['a', 'b']) // 'b'; + * Arr.last([]) // null; + * ``` + * + * @returns + * @since 4.0.0 + */ +export const last = (t: t): Option.t => t[t.length - 1] ?? null as Option.t; + +/** + * Returns the last element of an array `a` or `b` if the array is empty. + * + * @example + * + * ```ts + * import { Arr } from 'tiinvo'; + * + * Arr.lastOr([10, 20], 0) // 20 + * Arr.lastOr(0)([10, 20]) // 20 + * Arr.lastOr([], 0) // 0 + * Arr.lastOr(0)([]) // 0 + * ``` + * + * @since 4.0.0 + */ +export function lastOr(t: t, b: a): Option.t; +export function lastOr(t: a): Fn.Unary, Option.t>; +export function lastOr(t: t | a, b?: a): any { + if (guard(t)) { + return t[t.length - 1] ?? b; + } + + return (b: t) => b[b.length - 1] ?? t; +} + +/** + * Gets the length of the array. This is a number one higher than the highest index in the array. + * + * @example + * + * ```ts + * import { Arr } from 'tiinvo'; + * + * Arr.length([]) // 0 + * Arr.length([1]) // 1 + * Arr.length([1, 2, 3]) // 3 + * ``` + * + * @since 4.0.0 + */ +export const length = (t: t): number => t.length; + +/** + * Adds all the elements of an array into a string, separated by the specified separator string. + * + * **Important**, despites native JavaScript implementation, the default separator is an empty string if not specified + * + * @example + * + * ```ts + * import { Arr } from 'tiinvo' + * + * const x = [10, 20, 30] + * + * Arr.join(x, '-') // '10-20-30' + * Arr.join('-')(x) // '10-20-30' + * Arr.join(x) // '102030' + * Arr.join()(x) // '102030' + * ``` + * + * @since 4.0.0 + */ +export function join(a: a, b?: b): string; +export function join(a?: b): Fn.Unary; +export function join(a: any, b?: any): any { + if (guard(a)) { + return a.join(b ?? ''); + } + + return (b: a) => b.join(a ?? ''); +} + +/** + * Creates a new array `t` of a given size. + * + * If a default value `d` is specified, then the returning array will be `t` + * + * The default value could be either an arbitrary type or a `Fn.Unary` type. + * + * If a unary function is passed as `d`, the returning array will be `t>`. + * + * @example + * + * ```ts + * import { Arr } from 'tiinvo' + * + * Arr.make(3) // [undefined, undefined, undefined] + * Arr.make(3, 'hello') // ['hello', 'hello', 'hello'] + * Arr.make(3, x => ((x + 1) * 10).toString(16)) // ['a', '14', '1e'] + * ``` + * + * @since 4.0.0 + */ +export const make = (size: number, d?: a | Fn.Unary): a extends Option.None ? t : t => { + type b = a extends Option.None ? t> : t; + const a = [] as b; + + for (let i = 0; i < size; i++) { + if (typeof d === 'function') { + a.push((d as Fn.Unary)(i) as any); + } else { + a.push(d as any); + } + } + + return a; +}; + +/** + * Maps an array of elements `a` to an array of elements `b` using the mapping function `m`. + * + * @example + * + * ```ts + * import { Arr, Num } from 'tiinvo'; + * + * const x = [1, 2, 3]; + * const m = Num.mul(2); + * + * Arr.map(x, m) // [2, 4, 6] + * Arr.map(m)(x) // [2, 4, 6] + * ``` + * + * @since 4.0.0 + */ +export function map(a: t, m: Functors.Mappable): t; +export function map(a: Functors.Mappable): Fn.Unary, t>; +export function map(a: t | Functors.Mappable, m?: any): any { + if (guard(a)) { + return a.map(m); + } + + return (b: t) => b.map(a); +} + +/** + * Returns true if all elements of `a` do not meet the condition specified in the predicate `p`. + * + * @example + * + * ```ts + * import { Arr, Num } from 'tiinvo' + * + * const x = [1, 3, 5] + * const p = Num.isEven + * + * Arr.none(x, p) // true + * Arr.none(p)(x) // true + * ``` + * + * @since 4.0.0 + */ +export function none(a: t, m: Predicate.t): boolean; +export function none(a: Predicate.t): Fn.Unary, boolean>; +export function none(a: t | Predicate.t, m?: Predicate.t): any { + if (guard(a) && predicateguard(m)) { + return !a.some(m); + } + + return (b: t) => !b.some(a as Predicate.t); +} + +/** + * Returns a new array from a set of elements. + * + * ```typescript + * import { Arr } 'tiinvo'; + * + * Arr.of(1, 2, 3) // [1, 2, 3] + * ``` + * + * @param a + * + * @returns + * @since 4.0.0 + */ +export const of = Array.of; +/** + * Returns a random element of an array `a`. + * + * ```typescript + * import { Arr } 'tiinvo'; + * + * Arr.random(['a', 'b', 'c']) // 'a' or 'b' or 'c' + * ``` + * + * @param a + * @returns + * @since 4.0.0 + */ +export const random = (a: a[]) => a[Math.floor(Math.random() * a.length)]; + +/** + * Calls the specified callback function for all the elements in an array. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function. + * + * @example + * + * ```ts + * import { Arr, Num } from 'tiinvo' + * + * const x = [1, 2, 3, 4, 5] + * + * Arr.reduce(x, Num.add, 0) // 15 + * Arr.reduce(Num.add, 0)(x) // 15 + * ``` + * + * @since 4.0.0 + */ +export function reduce(a: t, r: Reducer, b: b): b; +export function reduce(a: Reducer, r: b): (b: t) => b; +export function reduce(a: t | Reducer, r: any, b?: any): any { + if (guard(a)) { + return a.reduce(r, b); + } + + return (c: t) => c.reduce(a, r); +} + +/** + * Calls the specified callback function for all the elements in an array, in descending order. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function. + * + * @example + * + * ```ts + * import { Arr, Num } from 'tiinvo' + * + * const x = [1, 2, 3, 4, 5] + * + * Arr.reduceRight(x, Num.sub, 0) // -15 + * Arr.reduceRight(Num.sub, 0)(x) // -15 + * ``` + * + * @since 4.0.0 + */ +export function reduceRight(a: t, r: Reducer, b: b): b; +export function reduceRight(a: Reducer, r: b): (b: t) => b; +export function reduceRight(a: t | Reducer, r: any, b?: any): any { + if (guard(a)) { + return a.reduceRight(r, b); + } + + return (c: t) => c.reduceRight(a, r); +} + +/** + * Reverses the elements in an array in place without mutating the original array. + * + * @example + * + * ```ts + * import { Arr } 'tiinvo'; + * + * Arr.reverse([10, 20, 30]) // [30, 20, 10] + * ``` + * + * @since 4.0.0 + */ +export const reverse = (a: t): t => a.reverse(); + +/** + * Shuffles an array + * + * @example + * + * ```ts + * import { Arr } from 'tiinvo' + * + * Arr.shuffle([10, 20, 30]) // could be [10, 30, 20] or [20, 30, 10] or [30, 20, 10] or ... + * ``` + * + * @since 4.0.0 + */ +export const shuffle = (a: t): t => { + const b: a[] = [].slice.call(a); + + for (let i = b.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [b[i], b[j]] = [b[j], b[i]]; + } + + return b; +}; + +/** + * Returns a copy of a section of an array. For both start and end, a negative index can be used to indicate an offset from the end of the array. For example, -2 refers to the second to last element of the array. + * + * @example + * + * ```ts + * import { Arr } from 'tiinvo' + * + * const x = [10, 20, 30] + * + * Arr.slice(x) // [10, 20, 30] + * Arr.slice(x, 2) // [30] + * Arr.slice(x, 1, 2) // [20] + * Arr.slice()(x) // [10, 20, 30] + * Arr.slice(2)(x) // [30] + * Arr.slice(1, 2)(x) // [20] + * ``` + * + * @since 4.0.0 + */ +export function slice>(a: a, s?: number, e?: number): a; +export function slice>(a?: number, s?: number): Fn.Unary; +export function slice>(a: a | number, s?: number, e?: number): any { + if (guard(a)) { + return a.slice(s, e); + } + + return (b: a) => b.slice(a, s); +} + +/** + * Determines whether some members of an array `a` satisfy the specified predicate `p`. + * + * @example + * + * ```ts + * import { Arr, Num } from 'tiinvo' + * + * const x = [1, 2, 3] + * const p = Num.isEven + * + * Arr.some(x, p) // true + * Arr.some(p)(x) // true + * ``` + * + * @since 4.0.0 + */ +export function some(a: t, p: Predicate.t): boolean; +export function some(a: Predicate.t): Fn.Unary, boolean>; +export function some(a: t | Predicate.t, p?: Predicate.t): any { + if (guard(a) && predicateguard(p)) { + return a.some(p); + } + + return (b: t) => b.some(a as Predicate.t); +} + +/** + * Sorts an array of elements `a` using the specified comparator `cmp`. + * + * @example + * + * ```ts + * import { Arr, Num } from 'tiinvo' + * + * const x = [3, 1, 2, 5, 4] + * const s = Num.asc; + * + * Arr.sort(x, s) // [1, 2, 3, 4, 5] + * Arr.sort(s)(x) // [1, 2, 3, 4, 5] + * ``` + * + * @since 4.0.0 + */ +export function sort(a: t, cmp: Functors.Comparable): t; +export function sort(a: Functors.Comparable): Fn.Unary, t>; +export function sort(a: t | Functors.Comparable, cmp?: Functors.Comparable): any { + if (guard(a)) { + return a.sort(cmp); + } + + return (b: t) => b.sort(a); +} + + +//#endregion + +//#region predicates + +/** + * Returns `true` if the array `a[]` is empty. + * + * ```ts + * import { Arr } from 'tiinvo'; + * + * Arr.empty([]) // true + * Arr.empty(['a']) // false + * ``` + * + * @param a + * @returns + * @since 4.0.0 + */ +export const empty = (t: t): boolean => t.length === 0; + +/** + * Returns `true` if the array `a[]` is populated. + * + * ```ts + * import { Arr } from 'tiinvo'; + * + * Arr.populated([]) // false + * Arr.populated(['a']) // true + * ``` + * + * @param a + * @returns + * @since 4.0.0 + */ +export const populated = (t: t): boolean => t.length > 0; + +//#endregion \ No newline at end of file diff --git a/src/Assert.test.ts b/src/Assert.test.ts new file mode 100644 index 0000000..63cc43b --- /dev/null +++ b/src/Assert.test.ts @@ -0,0 +1,51 @@ +import { describe, expect, test } from 'vitest'; +import * as Assert from './Assert.js'; +import * as Num from './Num.js'; + +describe(`Assert`, () => { + test(Assert.check.name, () => { + expect(() => Assert.check(true, 'will not throw')).not.toThrow(); + expect(() => Assert.check(false, 'yup it throws')).toThrow(); + expect(() => Assert.check('will not throw')(true)).not.toThrow(); + expect(() => Assert.check('yup it throws')(false)).toThrow(); + }); + + test(Assert.checkResult.name, () => { + expect(Assert.checkResult(true, 'will not throw')).toEqual(true); + expect(Assert.checkResult(false, 'yup it throws')).toEqual(Error("yup it throws")); + expect(Assert.checkResult('will not throw')(true)).toEqual(true); + expect(Assert.checkResult('yup it throws')(false)).toEqual(Error("yup it throws")); + }); + + test(Assert.make.name, () => { + const check0 = Assert.make(Num.isEven, 'number is not even'); + const check1 = Assert.make(Num.isEven, x => `number ${x} is not even`); + const check2 = Assert.make('number is not even')(Num.isEven); + const check3 = Assert.make((x: number) => `number ${x} is not even`)(Num.isEven); + + expect(() => check0(10)).not.toThrow(); + expect(() => check0(11)).toThrowError(Error("number is not even")); + expect(() => check1(10)).not.toThrow(); + expect(() => check1(11)).toThrowError(Error("number 11 is not even")); + expect(() => check2(10)).not.toThrow(); + expect(() => check2(11)).toThrowError(Error("number is not even")); + expect(() => check3(10)).not.toThrow(); + expect(() => check3(11)).toThrowError(Error("number 11 is not even")); + }); + + test(Assert.makeResult.name, () => { + const check0 = Assert.makeResult(Num.isEven, 'number is not even'); + const check1 = Assert.makeResult(Num.isEven, x => `number ${x} is not even`); + const check2 = Assert.makeResult('number is not even')(Num.isEven); + const check3 = Assert.makeResult(x => `number ${x} is not even`)(Num.isEven); + + expect(check0(10)).toEqual(true); + expect(check0(11)).toEqual(Error("number is not even")); + expect(check1(10)).toEqual(true); + expect(check1(11)).toEqual(Error("number 11 is not even")); + expect(check2(10)).toEqual(true); + expect(check2(11)).toEqual(Error("number is not even")); + expect(check3(10)).toEqual(true); + expect(check3(11)).toEqual(Error("number 11 is not even")); + }); +}); \ No newline at end of file diff --git a/src/Assert.ts b/src/Assert.ts new file mode 100644 index 0000000..7747dbb --- /dev/null +++ b/src/Assert.ts @@ -0,0 +1,130 @@ +import type * as Fn from './Fn.js'; +import type * as Functors from './Functors.js'; +import type * as Predicate from './Predicate.js'; +import { guard as pGuard } from './Predicate.js'; +import type * as Result from './Result.js'; +import { guard as strGuard } from './Str.js'; + +/** + * Asserts that a specified `condition` is true, otherwise throws an error with the given `errorMessage` + * + * @example + * + * ```ts + * import { Assert } from 'tiinvo' + * + * Assert.check(true, 'will not throw') // does not throw + * Assert.check(false, 'yup it throws') // throws + * Assert.check('will not throw')(true) // does not throw + * Assert.check('yup it throws')(false) // throws + * ``` + * + * @since 4.0.0 + */ +export function check(condition: boolean, errorMessage: string): void; +export function check(condition: string): Fn.Unary; +export function check(condition: boolean | string, errorMessage?: any): any { + if (typeof condition === 'boolean' && !condition && strGuard(errorMessage)) { + throw new Error(errorMessage); + } + + if (strGuard(condition)) { + return (b: boolean) => { + if (!b) { + throw new Error(condition); + } + }; + } +}; + +/** + * Asserts that a specified `condition` is true and returns it, otherwise returns `Result.Err` with the given `errorMessage` + * + * @example + * + * ```ts + * import { Assert } from 'tiinvo' + * + * Assert.checkResult(true, 'will not throw') // true + * Assert.checkResult(false, 'yup it throws') // Error("yup it throws") + * Assert.checkResult('will not throw')(true) // true + * Assert.checkResult('yup it throws')(false) // Error("yup it throws") + * ``` + * + * @since 4.0.0 + */ +export function checkResult(condition: boolean, errorMessage: string): Result.t; +export function checkResult(condition: string): Fn.Unary>; +export function checkResult(condition: boolean | string, errorMessage?: any): any { + if (typeof condition === 'boolean' && strGuard(errorMessage)) { + return !condition ? new Error(errorMessage) : true; + } + + if (strGuard(condition)) { + return (b: boolean) => { + if (!b) { + return new Error(condition); + } + + return true; + }; + } +}; + +/** + * Creates a check function starting from a `Predicate.t` and a message. + * + * @example + * + * ```ts + * import { Assert, Num } from 'tiinvo' + * + * const check0 = Assert.make(Num.isEven, 'number is not even') + * const check1 = Assert.make(Num.isEven, x => `number ${x} is not even`) + * + * check0(10) // does not throw + * check0(11) // throws "number is not even" + * check1(10) // does not throw + * check1(11) // throws "number 11 is not even" + * ``` + * + * @since 4.0.0 + */ +export function make(p: Predicate.t, m: string | Functors.Mappable): Fn.Unary; +export function make(p: string | Functors.Mappable): Fn.Unary, Fn.Unary>; +export function make(p: string | Functors.Mappable | Predicate.t, m?: any): any { + if (pGuard(p) && arguments.length === 2) { + return (a: a) => check(p(a), typeof m === 'function' ? m(a) : m); + } + + return (y: Predicate.t) => (a: a) => check(y(a), typeof p === 'function' ? String(p(a)) : p as string); +} + +/** + * Creates a check function starting from a `Predicate.t` and a message. + * + * @example + * + * ```ts + * import { Assert, Num } from 'tiinvo' + * + * const check0 = Assert.makeResult(Num.isEven, 'number is not even') + * const check1 = Assert.makeResult(Num.isEven, x => `number ${x} is not even`) + * + * check0(10) // true + * check0(11) // Error("number is not even") + * check1(10) // true + * check1(11) // Error("number 11 is not even") + * ``` + * + * @since 4.0.0 + */ +export function makeResult(p: Predicate.t, m: string | Functors.Mappable): Fn.Unary>; +export function makeResult(p: string | Functors.Mappable): Fn.Unary, Fn.Unary>>; +export function makeResult(p: any, m?: any): any { + if (pGuard(p) && m) { + return (a: a) => checkResult(p(a), typeof m === 'function' ? m(a) : m); + } + + return (y: Predicate.t) => (a: a) => checkResult(y(a), typeof p === 'function' ? p(a) : p); +} diff --git a/src/BigInt.ts b/src/BigInt.ts new file mode 100644 index 0000000..bd87fc7 --- /dev/null +++ b/src/BigInt.ts @@ -0,0 +1,444 @@ +import type * as Functors from './Functors.js'; +import type * as Fn from './Fn.js'; + +export const t = 'bigint'; +export type t = bigint; + +//#region guards + +/** + * Checks if a parameter `x` is a `BigInt` + * + * @example + * + * ```ts + * import { BigInt } from 'tiinvo' + * + * BigInt.guard(10) // false + * BigInt.guard(10n) // true + * ``` + * + * @since 4.0.0 + */ +export const guard = (x: unknown): x is t => typeof x === t; + +//#endregion + +//#region comparables + +/** + * Compares two `BigInt` `a` and `b` + * + * returns + * + * - -1 if `a` is less than `b` + * - 0 if `a` is equal to `b` + * - 1 if `a` is more than `b` + * + * @example + * + * ```ts + * import { BigInt } from 'tiinvo' + * + * BigInt.cmp(1n, 0n) // 1 + * BigInt.cmp(0n, 0n) // 0 + * BigInt.cmp(0n, 1n) // -1 + * ``` + * + * @since 4.0.0 + */ +export const cmp: Functors.Comparable = (a, b) => a < b ? -1 : a > b ? 1 : 0; + +/** + * Checks if `a` and `b` are equal + * + * @example + * + * ```ts + * import { BigInt } from 'tiinvo' + * ``` + * + * @since 4.0.0 + */ +export const eq: Functors.Equatable = (a, b) => a === b; + +//#endregion + +//#region mappables + +/** + * Maps a bigint `a` to a value `Result.t` if a is `bigint`, otherwise returns `Err`. + * + * ```ts + * import { BigInt } from 'tiinvo'; + * + * const toHex = BigInt.map(x => '0x' + x.toString(16)) + * + * toHex(10n) // 0xa + * toHex("a") // Error("a is not a bigint") + * ``` + */ + export const map = (m: Functors.Mappable) => (a: t) => guard(a) ? m(a) : new Error("a is not a bigint"); + + /** + * Maps a bigint `a` to a value `Result.t` if a is `number`, otherwise returns `b`. + * + * ```ts + * import { BigInt } from 'tiinvo'; + * + * const toHex = BigInt.mapOr(x => '0x' + x.toString(16), "0x0") + * + * toHex(10n) // 0xa + * toHex("a") // 0x0 + * ``` + */ + export const mapOr = (m: Functors.Mappable, b: b) => (a: t) => guard(a) ? m(a) : b; + + //#endregion + +//#region operables + +/** + * Adds `a` to `b` if both specified, otherwise returns a `Unary` + * function which once called adds `b` to `a` + * + * @example + * + * ```ts + * import { BigInt } from 'tiinvo'; + * + * BigInt.add(5n, -2n) // 3n + * BigInt.add(5n, 12n) // 17n + * + * const add5 = BigInt.add(5n) + * + * add5(10n) // 15n + * ``` + */ + export function add(a: t, b: t): t; + export function add(a: t): Fn.Unary; + export function add(a: t, b?: t): any { + if (guard(b)) { + return a + b; + } + + return (c: t) => c + a; + } + + /** + * Divides `a` by `b` if both specified, otherwise returns a `Unary` + * function which once called divides `b` by `a` + * + * @example + * + * ```ts + * import { BigInt } from 'tiinvo'; + * + * BigInt.div(4n, 2n) // 2n + * BigInt.div(12n, 3n) // 4n + * + * const div2 = BigInt.div(2n) + * + * div2(4n) // 2n + * ``` + */ + export function div(a: t, b: t): t; + export function div(a: t): Fn.Unary; + export function div(a: t, b?: t): any { + if (guard(b)) { + return a / b; + } + + return (c: t) => c / a; + } + + /** + * Returns the modulus of `a % b` if `b` parameter is passed, + * otherwise returns a `Unary` + * function which once called returns the modulus of `b % a` + * + * @example + * + * ```ts + * import { BigInt } from 'tiinvo'; + * + * BigInt.mod(2n, 2n) // 0n + * BigInt.mod(3n, 2n) // 1n + * + * const mod2 = BigInt.mod(2n) + * + * mod2(10n) // 0n + * mod2(15n) // 1n + * ``` + */ + export function mod(a: t, b: t): t; + export function mod(a: t): Fn.Unary; + export function mod(a: t, b?: t): any { + if (guard(b)) { + return a % b; + } + + return (c: t) => c % a; + } + + /** + * Multiplies `a` to `b` if both specified, otherwise returns a `Unary` + * function which once called multiplies `b` to `a` + * + * @example + * + * ```ts + * import { BigInt } from 'tiinvo'; + * + * BigInt.mul(5n, -2n) // -10n + * BigInt.mul(5n, 12n) // 60n + * + * const mul5 = BigInt.mul(5n) + * + * mul5(10n) // 50n + * ``` + */ + export function mul(a: t, b: t): t; + export function mul(a: t): Fn.Unary; + export function mul(a: t, b?: t): any { + if (guard(b)) { + return a * b; + } + + return (c: t) => c * a; + } + + /** + * Elevates `a` by `b` if both specified, otherwise returns a `Unary` + * function which once called elevates `b` by `a` + * + * @example + * + * ```ts + * import { BigInt } from 'tiinvo'; + * + * BigInt.pow(2n, 3n) // 8n + * BigInt.pow(3n, 2n) // 9n + * + * const pow5 = BigInt.pow(5n) + * + * pow5(10n) // 100_000n + * ``` + */ + export function pow(a: t, b: t): t; + export function pow(a: t): Fn.Unary; + export function pow(a: t, b?: t): any { + if (guard(b)) { + return a ** b; + } + + return (c: t) => c ** a; + } + + /** + * Square root of `a` under `b` if both specified, otherwise returns a `Unary` + * function which once called returns the root of `b` under `a` + * + * @example + * + * ```ts + * import { BigInt } from 'tiinvo'; + * + * BigInt.root(4n, 2n) // 2n + * BigInt.root(9n, 2n) // 3n + * + * const root2 = BigInt.root(2n) + * + * root2(4n) // 2n + * root2(9n) // 3n + * ``` + */ + export function root(a: t, b: t): t; + export function root(a: t): Fn.Unary; + export function root(a: t, b?: t): any { + if (guard(b) && typeof a === t) { + return a ** (1n / b); + } + + return (c: t) => c ** (1n / a); + } + + /** + * Subtracts `b` to `a` if both specified, otherwise returns a `Unary` + * function which once called subtracts `a` to `b` + * + * @example + * + * ```ts + * import { BigInt } from 'tiinvo'; + * + * BigInt.sub(5, -2) // 7 + * BigInt.sub(5, 12) // -7 + * + * const sub5 = BigInt.sub(5) + * + * sub5(10) // 5 + * sub5(-2) // -7 + * ``` + */ + export function sub(a: t, b: t): t; + export function sub(a: t): Fn.Unary; + export function sub(a: t, b?: t): any { + if (guard(b)) { + return a - b; + } + + return (c: t) => c - a; + } + + //#endregion + +//#region sortables + +/** + * Compares two numbers `a` and `b` if `b` is defined, otherwise returns a + * `Unary` function which once called compares `b` and `a` + * + * Great to sort a numeric array in ASC direction. + * + * @example + * + * ```ts + * import { BigInt } from 'tiinvo'; + * + * const collection = [10n, 5n, 6n, 4n, 12n, 22n, 3n]; + * + * collection.sort(BigInt.asc) // [3n, 4n, 5n, 6n, 10n, 12n, 22n] + * ``` + * + * @since 4.0.0 + */ +export function asc(a: t, b: t): Functors.ComparableResult; +export function asc(a: t): Fn.Unary; +export function asc(a: t, b?: any): any { + if (guard(b)) { + return cmp(a, b); + } + + return (c: t) => cmp(c, a); +} + +/** + * Compares two numbers `b` and `a` if `b` is defined, otherwise returns a + * `Unary` function which once called compares `a` and `b` + * + * Great to sort a numeric array in DESC direction. + * + * @example + * + * ```ts + * import { BigInt } from 'tiinvo'; + * + * const collection = [10n, 5n, 6n, 4n, 12n, 22n, 3n]; + * + * collection.sort(BigInt.desc) // [22n, 12n, 10n, 6n, 5n, 4n, 3n] + * ``` + * + * @since 4.0.0 + */ +export function desc(a: t, b: t): Functors.ComparableResult; +export function desc(a: t): Fn.Unary; +export function desc(a: t, b?: any): any { + if (guard(b)) { + return cmp(b, a); + } + + return (c: t) => cmp(a, c); +} + +//#endregion + +//#region serializables + +/** + * Returns a bigint in binary notation. + * + * If the passed argument at runtime is not a bigint, an Error will be returned. + * + * @example + * + * ```ts + * import { BigInt } from 'tiinvo'; + * + * BigInt.toBin(10) // "0b1010" + * ``` + * + * @since 4.0.0 + */ + export const toBin = map(x => '0b' + x.toString(2)); + + /** + * Returns a bigint in hexadecimal notation + * + * If the passed argument at runtime is not a bigint, an Error will be returned. + * + * @example + * + * ```ts + * import { BigInt } from 'tiinvo'; + * + * BigInt.toHex(10) // "0xa" + * ``` + * + * @since 4.0.0 + */ + export const toHex = map(x => '0x' + x.toString(16)); + + /** + * Returns a bigint in octal notation + * + * If the passed argument at runtime is not a bigint, an Error will be returned. + * + * @example + * + * ```ts + * import { BigInt } from 'tiinvo'; + * + * BigInt.toOct(10) // "0o12" + * ``` + * + * @since 4.0.0 + */ + export const toOct = map(x => '0o' + x.toString(8)); + + /** + * Returns a bigint in json notation. + * + * If the passed argument at runtime is not a bigint, an Error will be returned. + * + * @example + * + * ```ts + * import { BigInt } from 'tiinvo'; + * + * BigInt.toJSON(10) // "10" + * ``` + * + * @since 4.0.0 + */ + export const toJSON = map(JSON.stringify); + + /** + * Returns a stringified number. + * + * If the passed argument at runtime is not a bigint, an Error will be returned. + * + * @example + * + * ```ts + * import { BigInt } from 'tiinvo'; + * + * BigInt.toString(10) // "10" + * ``` + * + * @since 4.0.0 + */ + export const toString = map(String); + + //#endregion + \ No newline at end of file diff --git a/src/Bool.test.ts b/src/Bool.test.ts new file mode 100644 index 0000000..c91f70e --- /dev/null +++ b/src/Bool.test.ts @@ -0,0 +1,12 @@ +import { describe, expect, test } from 'vitest'; +import * as Bool from './Bool.js'; + +describe(`Bool`, () => { + test(Bool.flip.name, () => { + expect(Bool.flip(true)).toEqual(false); + }); + test(Bool.guard.name, () => { + expect(Bool.guard(true)).toEqual(true); + expect(Bool.guard(1000)).toEqual(false); + }); +}); \ No newline at end of file diff --git a/src/Catch.test.ts b/src/Catch.test.ts new file mode 100644 index 0000000..0034896 --- /dev/null +++ b/src/Catch.test.ts @@ -0,0 +1,32 @@ +import { describe, expect, test } from 'vitest'; +import * as Catch from './Catch.js'; +import * as Functors from './Functors.js'; +import * as Num from './Num.js'; + +describe("Catch", () => { + test(Catch.make.name, () => { + function catchy(arg: number) { + if (Num.isEven(arg)) { + throw new TypeError("I expected an odd number :("); + } + + return arg; + } + + const c: Functors.CatchableModule = { + [Functors.catchableSync]() { + return { + catch: (_, args) => args[0] - 1, + func: catchy, + }; + } + }; + + const catched = Catch.make(c); + + expect(catched(10)).toEqual(9); + expect(catched(7)).toEqual(7); + + expect(() => Catch.make({} as any)).toThrow(); + }); +}); diff --git a/src/Catch.ts b/src/Catch.ts new file mode 100644 index 0000000..4f5149a --- /dev/null +++ b/src/Catch.ts @@ -0,0 +1,63 @@ +import type * as Fn from './Fn.js'; +import type * as Functors from './Functors.js'; +import { catchableAsync, catchableSync } from './Functors.js'; + +/** + * Handles a `Functors.Catchable` + * + * @example + * + * ```ts + * import { Catch, Functors, Num } from 'tiinvo' + * + * function catchy(arg: number) { + * if (Num.isEven(arg)) { + * throw new TypeError("I expected an odd number :(") + * } + * + * return arg; + * } + * + * const c: Functors.CatchableModule = { + * [Functors.catchableSync]() { + * return { + * catch: (_error, args) => args[0] - 1, + * func: catchy, + * } + * } + * } + * + * const catched = Catch.make(c); + * + * catched(10) // 9 + * catched(7) // 7 + * ``` + * + * @since 4.0.0 + */ +export const make = (catchable: Functors.CatchableModule): f extends Fn.AnyAsyncFn ? ((...args: Parameters) => Promise>>) : ((...args: Parameters) => ReturnType) => { + type args = Parameters; + + const modAsync = (catchable as Functors.CatchableAsyncModule)[catchableAsync]?.(); + const modSync = (catchable as Functors.CatchableSyncModule)[catchableSync]?.(); + + if (modAsync) { + return (async (...args: args): Promise>> => { + try { + return await modAsync.func.apply(null, args); + } catch (error: unknown) { + return await modAsync.catch(error as Error, args) as any; + } + }) as any; + } else if (modSync) { + return ((...args: args): ReturnType => { + try { + return modSync.func.apply(null, args); + } catch (error: unknown) { + return modSync.catch(error as Error, args); + } + }) as any; + } + + throw new Error(`Invalid Catchable passed`); +}; \ No newline at end of file diff --git a/src/Fn.test.ts b/src/Fn.test.ts new file mode 100644 index 0000000..731bb0f --- /dev/null +++ b/src/Fn.test.ts @@ -0,0 +1,46 @@ +import { describe, expect, test } from 'vitest'; +import * as Fn from './Fn.js'; +import * as Num from './Num.js'; + +describe(`Fn`, () => { + test(Fn.cmp.name, () => { + const t0: Fn.AnyFn = (a, b, c, d) => 0; + expect(Fn.cmp(Num.add, Num.add)).toEqual(0); + expect(Fn.cmp(Num.add, Num.sub)).toEqual(-1); + expect(Fn.cmp(Num.sub, Num.add)).toEqual(1); + expect(Fn.cmp(t0, Num.add)).toEqual(1); + expect(Fn.cmp(Num.add, t0)).toEqual(-1); + }); + + test(Fn.eq.name, () => { + expect(Fn.eq(Num.add, Num.add)).toEqual(true); + expect(Fn.eq(Num.add, Num.sub)).toEqual(false); + expect(Fn.eq(Num.sub, Num.add)).toEqual(false); + }); + + test(Fn.guard.name, () => { + expect(Fn.guard(10)).toEqual(false); + expect(Fn.guard(() => { })).toEqual(true); + }); + + test(Fn.length.name, () => { + expect(Fn.length(Fn.cmp)).toEqual(2); + expect(Fn.length(Fn.length)).toEqual(1); + }); + + test(Fn.map.name, () => { + const m = Fn.map(Num.add(1), Num.mul(2), Num.sub(3), Num.pow(4)); + + expect(m(2)).toEqual([3, 4, -1, 16]); + }); + + test(Fn.name.name, () => { + expect(Fn.name(Fn.cmp)).toEqual('cmp'); + expect(Fn.name(Fn.name)).toEqual('name'); + }); + + test(Fn.pass.name, () => { + expect(Fn.pass(10)).toEqual(10); + expect(Fn.pass(20)).toEqual(20); + }); +}); diff --git a/src/Num.test.ts b/src/Num.test.ts new file mode 100644 index 0000000..c3b7bf9 --- /dev/null +++ b/src/Num.test.ts @@ -0,0 +1,234 @@ +import { describe, expect, test } from 'vitest'; +import * as Num from './Num.js'; + +describe("Num", () => { + test(Num.add.name, () => { + expect(Num.add(5, -2)).toEqual(3); + expect(Num.add(5, 12)).toEqual(17); + + const add5 = Num.add(5); + + expect(add5(10)).toEqual(15); + }); + + test(Num.asc.name, () => { + const collection = [10, 5, 6, 4, 12, 22, 3]; + + expect(collection.sort(Num.asc)).toEqual([3, 4, 5, 6, 10, 12, 22]); + expect(Num.asc(10)(2)).toEqual(-1); + }); + + test(Num.cmp.name, () => { + expect(Num.cmp(1, 1)).toEqual(0); + expect(Num.cmp(1, 0)).toEqual(1); + expect(Num.cmp(0, 1)).toEqual(-1); + }); + + test(Num.desc.name, () => { + const collection = [10, 5, 6, 4, 12, 22, 3]; + + expect(collection.sort(Num.desc)).toEqual([22, 12, 10, 6, 5, 4, 3]); + expect(Num.desc(10)(1)).toEqual(1); + }); + + test(Num.div.name, () => { + expect(Num.div(4, 2)).toEqual(2); + expect(Num.div(12, 3)).toEqual(4); + + const div2 = Num.div(2); + + expect(div2(4)).toEqual(2); + }); + + test(Num.eq.name, () => { + expect(Num.eq(1, 1)).toEqual(true); + expect(Num.eq(1, 0)).toEqual(false); + expect(Num.eq(0, 1)).toEqual(false); + }); + + test(Num.gt.name, () => { + expect(Num.gt(5, -2)).toEqual(true); + expect(Num.gt(5, 12)).toEqual(false); + + const gt5 = Num.gt(5); + + expect(gt5(10)).toEqual(true); + expect(gt5(-2)).toEqual(false); + }); + + test(Num.gte.name, () => { + expect(Num.gte(5, -2)).toEqual(true); + expect(Num.gte(5, 12)).toEqual(false); + expect(Num.gte(10, 10)).toEqual(true); + + const gte5 = Num.gte(5); + + expect(gte5(10)).toEqual(true); + expect(gte5(5)).toEqual(true); + expect(gte5(-2)).toEqual(false); + }); + + test(Num.guard.name, () => { + const or0 = (x: unknown): number => Num.guard(x) ? x : 0; + + expect(or0(10)).toEqual(10); + expect(or0(20)).toEqual(20); + expect(or0(-1)).toEqual(-1); + expect(or0(4e12)).toEqual(4e12); + expect(or0('hello world')).toEqual(0); + expect(or0(true)).toEqual(0); + expect(or0(false)).toEqual(0); + expect(or0({})).toEqual(0); + }); + + test(Num.isEven.name, () => { + expect(Num.isEven(10)).toEqual(true); + expect(Num.isEven(91)).toEqual(false); + }); + + test(Num.isNegative.name, () => { + expect(Num.isNegative(-1)).toEqual(true); + expect(Num.isNegative(10)).toEqual(false); + }); + + test(Num.isOdd.name, () => { + expect(Num.isOdd(10)).toEqual(false); + expect(Num.isOdd(91)).toEqual(true); + }); + + test(Num.isPositive.name, () => { + expect(Num.isPositive(-1)).toEqual(false); + expect(Num.isPositive(10)).toEqual(true); + }); + + test(Num.lt.name, () => { + expect(Num.lt(5, -2)).toEqual(false); + expect(Num.lt(5, 12)).toEqual(true); + + const lt5 = Num.lt(5); + + expect(lt5(10)).toEqual(false); + expect(lt5(-2)).toEqual(true); + }); + + test(Num.lte.name, () => { + expect(Num.lte(5, -2)).toEqual(false); + expect(Num.lte(5, 12)).toEqual(true); + expect(Num.lte(5, 5)).toEqual(true); + + const lte5 = Num.lte(5); + + expect(lte5(5)).toEqual(true); + expect(lte5(10)).toEqual(false); + expect(lte5(-2)).toEqual(true); + }); + + test(Num.map.name, () => { + const toHex = Num.map(x => '0x' + x.toString(16)); + + expect(toHex(10)).toEqual("0xa"); + expect(toHex("a" as any)).toEqual(new Error("a is not a number")); + }); + + test(Num.mapOr.name, () => { + const toHex = Num.mapOr(x => '0x' + x.toString(16), "0x0"); + + expect(toHex(10)).toEqual("0xa"); + expect(toHex("a" as any)).toEqual("0x0"); + }); + + test(Num.mod.name, () => { + expect(Num.mod(2, 2)).toEqual(0); + expect(Num.mod(3, 2)).toEqual(1); + + const mod2 = Num.mod(2); + + expect(mod2(10)).toEqual(0); + expect(mod2(15)).toEqual(1); + }); + + test(Num.mul.name, () => { + expect(Num.mul(5, -2)).toEqual(-10); + expect(Num.mul(5, 12)).toEqual(60); + + const mul5 = Num.mul(5); + + expect(mul5(10)).toEqual(50); + }); + + test(Num.ne.name, () => { + expect(Num.ne(5, -2)).toEqual(true); + expect(Num.ne(5, 12)).toEqual(true); + expect(Num.ne(5, 5)).toEqual(false); + + const ne5 = Num.ne(5); + + expect(ne5(5)).toEqual(false); + expect(ne5(10)).toEqual(true); + expect(ne5(-2)).toEqual(true); + }); + + test(Num.pow.name, () => { + expect(Num.pow(2, 3)).toEqual(8); + expect(Num.pow(3, 2)).toEqual(9); + + const pow5 = Num.pow(5); + + expect(pow5(10)).toEqual(100_000); + }); + + test(Num.root.name, () => { + expect(Num.root(4, 2)).toEqual(2); + expect(Num.root(9, 2)).toEqual(3); + + const root2 = Num.root(2); + + expect(root2(4)).toEqual(2); + expect(root2(9)).toEqual(3); + }); + + test(Num.sub.name, () => { + expect(Num.sub(5, -2)).toEqual(7); + expect(Num.sub(5, 12)).toEqual(-7); + + const sub5 = Num.sub(5); + + expect(sub5(10)).toEqual(5); + expect(sub5(-2)).toEqual(-7); + }); + + test(Num.toBin.name, () => { + expect(Num.toBin(10)).toEqual('0b1010'); + }); + + test(Num.toExponential.name, () => { + expect(Num.toExponential(10, 2)).toEqual("1.00e+1"); + expect(Num.toExponential(10)(2)).toEqual("2.0000000000e+0"); + }); + + test(Num.toFixed.name, () => { + expect(Num.toFixed(10.505, 2)).toEqual("10.51"); + expect(Num.toFixed(10.505)(2)).toEqual("2.0000000000"); + }); + + test(Num.toHex.name, () => { + expect(Num.toHex(10)).toEqual("0xa"); + }); + + test(Num.toJSON.name, () => { + expect(Num.toJSON(10)).toEqual("10"); + }); + + test(Num.toOct.name, () => { + expect(Num.toOct(10)).toEqual("0o12"); + }); + + test(Num.toPrecision.name, () => { + expect(Num.toPrecision(10, 2)).toEqual("10"); + expect(Num.toPrecision(10)(2)).toEqual("2.000000000"); + }); + + test(Num.toString.name, () => { + expect(Num.toString(10)).toEqual("10"); + }); +}); diff --git a/src/Obj.test.ts b/src/Obj.test.ts new file mode 100644 index 0000000..ba6e243 --- /dev/null +++ b/src/Obj.test.ts @@ -0,0 +1,114 @@ +import { describe, expect, test } from 'vitest'; +import * as Bool from './Bool.js'; +import * as Num from './Num.js'; +import * as Obj from './Obj.js'; +import * as Str from './Str.js'; + +describe("Obj", () => { + test(Obj.guard.name, () => { + expect(Obj.guard({})).toEqual(true); + expect(Obj.guard(null)).toEqual(false); + }); + + test(Obj.guardOf.name, () => { + const guardStruct = Obj.guardOf({ + a: Str.guard, + b: Num.guard, + c: Bool.guard + }); + + expect(guardStruct({ a: `foo`, b: 1, c: true })).toEqual(true); + expect(guardStruct({ a: `foo`, b: false, c: 1 })).toEqual(false); + expect(guardStruct(10)).toEqual(false); + expect(() => Obj.guardOf(10)).toThrow(); + + const rectest = Obj.guardOf({ + y: { + z: Num.guard, + }, + x: guardStruct, + }); + + expect(rectest({ x: { a: `foo`, b: 1, c: true }, y: { z: 10 } })).toEqual(true); + }); + + test(Obj.hasKey.name, () => { + const hasa = Obj.hasKey('a'); + + expect(hasa({})).toEqual(false); + expect(hasa({ a: 1 })).toEqual(true); + }); + + test(Obj.hasKeyOf.name, () => { + const hasa = Obj.hasKeyOf('a', Num.guard); + + expect(hasa({})).toEqual(false); + expect(hasa({ a: 1 })).toEqual(true); + expect(hasa({ a: `nope` })).toEqual(false); + expect(Obj.hasKeyOf('a', Num.guard, { a: 10 })).toEqual(true); + }); + + test(Obj.assign.name, () => { + const a = { a: 1, b: 2 }; + const b = { b: 3, c: 4 }; + const c = { c: 5, d: 6 }; + + expect(Obj.assign(a, b, c)).toEqual({ a: 1, b: 3, c: 5, d: 6 }); + }); + + test(Obj.entries.name, () => { + expect(Obj.entries({ a: 1, b: 2 })).toEqual([['a', 1], ['b', 2]]); + }); + + test(Obj.freeze.name, () => { + const a = { a: 1, b: 2 }; + Obj.freeze(a); + expect(() => a.a = 100).toThrow(); + }); + + test(Obj.fromEntries.name, () => { + expect(Obj.fromEntries([['a', 1], ['b', 2]])).toEqual({ a: 1, b: 2 }); + }); + + test(Obj.get.name, () => { + const get = Obj.get(`foo`); + + expect(get({ foo: `bar` })).toEqual(`bar`); + expect(get({})).toEqual(null); + }); + + test(Obj.omit.name, () => { + const omit = Obj.omit(['a', 'b']); + expect(omit({ a: 1, b: 2, c: 3 })).toEqual({ c: 3 }); + expect(omit({ a: 1, b: 2 })).toEqual({}); + expect(Obj.omit(['a', 'b'], { a: 1, b: 2 })).toEqual({}); + }); + + test(Obj.pick.name, () => { + const pick = Obj.pick(['a', 'b']); + expect(pick({ a: 1, b: 2, c: 3 })).toEqual({ a: 1, b: 2 }); + expect(pick({ a: 1, b: 2 })).toEqual({ a: 1, b: 2 }); + expect(Obj.pick(['a', 'b'], { a: 1, b: 2, c: 3, d: 3 })).toEqual({ a: 1, b: 2 }); + }); + + test(Obj.map.name, () => { + expect(Obj.map(Object.keys)({ a: 10, b: 20 })).toEqual(['a', 'b']); + expect(Obj.map((x: Record) => x.a ?? 0)({ a: 10 })).toEqual(10); + expect(Obj.map((x: Record) => x.a ?? 0)({ b: 10 })).toEqual(0); + expect(Obj.map((x: Record) => x.a ?? 0)(10 as any)).toEqual(null); + }); + + test(Obj.keys.name, () => { + expect(Obj.keys({ a: 10, b: 20 })).toEqual(['a', 'b']); + }); + + test(Obj.size.name, () => { + expect(Obj.size({ a: 1, b: 2 })).toEqual(2); + expect(Obj.size({})).toEqual(0); + }); + + test(Obj.values.name, () => { + expect(Obj.values({ a: 1, b: 2 })).toEqual([1, 2]); + expect(Obj.values({})).toEqual([]); + }); +}); \ No newline at end of file diff --git a/src/Option.test.ts b/src/Option.test.ts new file mode 100644 index 0000000..126a091 --- /dev/null +++ b/src/Option.test.ts @@ -0,0 +1,114 @@ +import { describe, expect, test } from 'vitest'; +import * as Option from './Option.js'; +import * as Num from './Num.js'; +import * as Str from './Str.js'; + +describe("Option", () => { + test(Option.isNone.name, () => { + expect(Option.isNone(1)).toEqual(false); + expect(Option.isNone(null)).toEqual(true); + expect(Option.isNone(undefined)).toEqual(true); + }); + + test(Option.isSome.name, () => { + expect(Option.isSome(1)).toEqual(true); + expect(Option.isSome(null)).toEqual(false); + expect(Option.isSome(undefined)).toEqual(false); + }); + + test(Option.guardOf.name, () => { + const x = 1; + const y = null; + const z = undefined; + const w = `a`; + + const isnumsome = Option.guardOf(Num.guard); + + expect(isnumsome(x)).toEqual(true); + expect(isnumsome(y)).toEqual(true); + expect(isnumsome(z)).toEqual(true); + expect(isnumsome(w)).toEqual(false); + }); + + test(Option.cmp.name, () => { + const cmp = Option.cmp(Str.cmp); + + expect(cmp("a", "a")).toEqual(0); + expect(cmp("a", "b")).toEqual(-1); + expect(cmp("b", "a")).toEqual(1); + expect(cmp(null, undefined)).toEqual(0); + expect(cmp(null, "a")).toEqual(-1); + expect(cmp("a", undefined)).toEqual(1); + }); + + test(Option.eq.name, () => { + const eq = Option.eq(Num.eq); + + expect(eq(0, 0)).toEqual(true); + expect(eq(null, undefined)).toEqual(true); + expect(eq(null, 0)).toEqual(false); + expect(eq(0, null)).toEqual(false); + expect(eq(1_000_000, 0)).toEqual(false); + }); + + test(Option.filter.name, () => { + const f = Option.filter(Num.gt(1)); + + expect(f(1)).toEqual(null); + expect(f(2)).toEqual(2); + expect(Option.filter(Num.gt(1), 2)).toEqual(2); + expect(f(null)).toEqual(null); + expect(Option.filter(Num.gt(2), 1)).toEqual(null); + expect(Option.filter(Num.gt(1), null)).toEqual(null); + }); + + test(Option.map.name, () => { + const m = Option.map(Num.add(1)); + + expect(m(1)).toEqual(2); + expect(m(null)).toEqual(null); + }); + + test(Option.mapOr.name, () => { + const m = Option.mapOr(Num.add(2), 0); + + expect(m(1)).toEqual(3); + expect(m(null)).toEqual(0); + }); + + test(Option.tryAsync.name, async () => { + const fn = async (arg: number) => { + if (Num.isEven(arg)) { + return arg * 2; + } + + throw new Error(`${arg} is not even`); + }; + + const safe = Option.tryAsync(fn); + + expect(await safe(2)).toEqual(4); + expect(await safe(3)).toEqual(null); + + expect(Option.isSome(await safe(2))).toEqual(true); + expect(Option.isNone(await safe(3))).toEqual(true); + }); + + test(Option.trySync.name, () => { + const fn = (arg: number) => { + if (Num.isEven(arg)) { + return arg * 2; + } + + throw new Error(`${arg} is not even`); + }; + + const safe = Option.trySync(fn); + + expect(safe(2)).toEqual(4); + expect(safe(3)).toEqual(null); + + expect(Option.isSome(safe(2))).toEqual(true); + expect(Option.isNone(safe(3))).toEqual(true); + }); +}); diff --git a/src/Pipe.test.ts b/src/Pipe.test.ts new file mode 100644 index 0000000..bea0a92 --- /dev/null +++ b/src/Pipe.test.ts @@ -0,0 +1,69 @@ +import { describe, expect, test, vitest } from 'vitest'; +import * as Pipe from './Pipe.js'; +import * as Num from './Num.js'; + +describe(`Pipe`, () => { + test(Pipe.async.name, async () => { + + async function test1(x: number) { + return x + 1; + } + async function test2(x: number) { + return x + 2; + } + async function test3(x: number) { + return x + 3; + } + + const x1 = vitest.fn(test1); + const x2 = vitest.fn(test2); + const x3 = vitest.fn(test3); + + const piped = Pipe.async(x1, x2, x3); + + expect(await piped(1)).toEqual(7); + expect(x1).toHaveBeenCalled(); + expect(x1).toBeCalledWith(1); + expect(x2).toHaveBeenCalled(); + expect(x2).toBeCalledWith(2); + expect(x3).toHaveBeenCalled(); + expect(x3).toBeCalledWith(4); + }); + test(Pipe.sync.name, () => { + const vat = Pipe.sync(Num.div(100), Num.mul(22)); + + expect(vat(100)).toEqual(22); + expect(vat(150)).toEqual(33); + expect(vat(200)).toEqual(44); + }); + + test(`o(n) pipe`, () => { + const f = Num.add(1); + // @ts-ignore + const p0 = Pipe.sync(); + const p1 = Pipe.sync(f); + const p2 = Pipe.sync(f, f); + const p3 = Pipe.sync(f, f, f); + const p4 = Pipe.sync(f, f, f, f); + const p5 = Pipe.sync(f, f, f, f, f); + const p6 = Pipe.sync(f, f, f, f, f, f); + const p7 = Pipe.sync(f, f, f, f, f, f, f); + const p8 = Pipe.sync(f, f, f, f, f, f, f, f); + const p9 = Pipe.sync(f, f, f, f, f, f, f, f, f); + const p10 = Pipe.sync(f, f, f, f, f, f, f, f, f, f); + const p11 = Pipe.sync(f, f, f, f, f, f, f, f, f, f, f); + + expect(p0(0)).toBe(0); + expect(p1(0)).toBe(1); + expect(p2(0)).toBe(2); + expect(p3(0)).toBe(3); + expect(p4(0)).toBe(4); + expect(p5(0)).toBe(5); + expect(p6(0)).toBe(6); + expect(p7(0)).toBe(7); + expect(p8(0)).toBe(8); + expect(p9(0)).toBe(9); + expect(p10(0)).toBe(10); + expect(p11(0)).toBe(11); + }); +}); diff --git a/src/Predicate.test.ts b/src/Predicate.test.ts new file mode 100644 index 0000000..9374cfc --- /dev/null +++ b/src/Predicate.test.ts @@ -0,0 +1,157 @@ +import { describe, expect, test } from 'vitest'; +import * as Predicate from './Predicate.js'; +import * as Num from './Num.js'; + +describe("Predicate", () => { + test(Predicate.and.name, () => { + const and = Predicate.and(Num.gt(3), Num.lt(10)); + + expect(and(5)).toEqual(true); + expect(and(2)).toEqual(false); + }); + + test(Predicate.eq.name, () => { + const eq0 = Predicate.eq(0); + + expect(eq0(10)).toEqual(false); + expect(eq0(0)).toEqual(true); + expect(Predicate.eq(0, 10)).toEqual(false); + expect(Predicate.eq(0, 0)).toEqual(true); + }); + + test(Predicate.guard.name, () => { + expect(Predicate.guard((x: number, b: number) => x + b)).toEqual(false); + expect(Predicate.guard((x: number) => x > 0)).toEqual(true); + }); + + test(Predicate.invert.name, () => { + const i = Predicate.invert(Num.gt(0)); + + expect(i(10)).toEqual(false); + expect(i(-1)).toEqual(true); + }); + + test(Predicate.neq.name, () => { + const neq = Predicate.neq(0); + + expect(neq(10)).toEqual(true); + expect(neq(0)).toEqual(false); + expect(Predicate.neq(0, 10)).toEqual(true); + expect(Predicate.neq(0, 0)).toEqual(false); + }); + + test(Predicate.none.name, () => { + const n = Predicate.none(Num.gt(0), Num.isEven); + + expect(n(4)).toEqual(false); + expect(n(5)).toEqual(false); + expect(n(-4)).toEqual(false); + expect(n(-5)).toEqual(true); + }); + + test(Predicate.or.name, () => { + const or = Predicate.or(Num.gt(0), Num.isEven); + + expect(or(-1)).toEqual(false); + expect(or(1)).toEqual(true); + expect(or(2)).toEqual(true); + }); + + test("and linearity", () => { + const and0 = Predicate.and(); + const and1 = Predicate.and(Num.gt(0)); + const and2 = Predicate.and(Num.gt(0), Num.gt(1)); + const and3 = Predicate.and(Num.gt(0), Num.gt(1), Num.gt(2)); + const and4 = Predicate.and(Num.gt(0), Num.gt(1), Num.gt(2), Num.gt(3)); + const and5 = Predicate.and(Num.gt(0), Num.gt(1), Num.gt(2), Num.gt(3), Num.gt(4)); + const and6 = Predicate.and(Num.gt(0), Num.gt(1), Num.gt(2), Num.gt(3), Num.gt(4), Num.gt(5)); + const and7 = Predicate.and(Num.gt(0), Num.gt(1), Num.gt(2), Num.gt(3), Num.gt(4), Num.gt(5), Num.gt(6)); + const and8 = Predicate.and(Num.gt(0), Num.gt(1), Num.gt(2), Num.gt(3), Num.gt(4), Num.gt(5), Num.gt(6), Num.gt(7)); + const and9 = Predicate.and(Num.gt(0), Num.gt(1), Num.gt(2), Num.gt(3), Num.gt(4), Num.gt(5), Num.gt(6), Num.gt(7), Num.gt(8)); + const and10 = Predicate.and(Num.gt(0), Num.gt(1), Num.gt(2), Num.gt(3), Num.gt(4), Num.gt(5), Num.gt(6), Num.gt(7), Num.gt(8), Num.gt(9)); + const and11 = Predicate.and(Num.gt(0), Num.gt(1), Num.gt(2), Num.gt(3), Num.gt(4), Num.gt(5), Num.gt(6), Num.gt(7), Num.gt(8), Num.gt(9), Num.gt(10)); + + expect(and0(0)).toBe(true); + expect(and1(1)).toBe(true); + expect(and2(2)).toBe(true); + expect(and3(3)).toBe(true); + expect(and4(4)).toBe(true); + expect(and5(5)).toBe(true); + expect(and6(6)).toBe(true); + expect(and7(7)).toBe(true); + expect(and8(8)).toBe(true); + expect(and9(9)).toBe(true); + expect(and10(10)).toBe(true); + expect(and11(11)).toBe(true); + expect(and0(-1)).toBe(true); + expect(and1(0)).toBe(false); + expect(and2(1)).toBe(false); + expect(and3(2)).toBe(false); + expect(and4(3)).toBe(false); + expect(and5(4)).toBe(false); + expect(and6(5)).toBe(false); + expect(and7(6)).toBe(false); + expect(and8(7)).toBe(false); + expect(and9(8)).toBe(false); + expect(and10(9)).toBe(false); + expect(and11(10)).toBe(false); + }); + + test(`linearity (or)`, () => { + const p = Predicate; + const or0 = p.or(); + const or1 = p.or(Num.eq(0)); + const or2 = p.or(Num.eq(0), Num.eq(1)); + const or3 = p.or(Num.eq(0), Num.eq(1), Num.eq(2)); + const or4 = p.or(Num.eq(0), Num.eq(1), Num.eq(2), Num.eq(3)); + const or5 = p.or(Num.eq(0), Num.eq(1), Num.eq(2), Num.eq(3), Num.eq(4)); + const or6 = p.or(Num.eq(0), Num.eq(1), Num.eq(2), Num.eq(3), Num.eq(4), Num.eq(5)); + const or7 = p.or(Num.eq(0), Num.eq(1), Num.eq(2), Num.eq(3), Num.eq(4), Num.eq(5), Num.eq(6)); + const or8 = p.or(Num.eq(0), Num.eq(1), Num.eq(2), Num.eq(3), Num.eq(4), Num.eq(5), Num.eq(6), Num.eq(7)); + const or9 = p.or(Num.eq(0), Num.eq(1), Num.eq(2), Num.eq(3), Num.eq(4), Num.eq(5), Num.eq(6), Num.eq(7), Num.eq(8)); + const or10 = p.or(Num.eq(0), Num.eq(1), Num.eq(2), Num.eq(3), Num.eq(4), Num.eq(5), Num.eq(6), Num.eq(7), Num.eq(8), Num.eq(9)); + const or11 = p.or(Num.eq(0), Num.eq(1), Num.eq(2), Num.eq(3), Num.eq(4), Num.eq(5), Num.eq(6), Num.eq(7), Num.eq(8), Num.eq(9), Num.eq(10)); + + expect(or0(0)).toBe(true); + expect(or1(0)).toBe(true); + expect(or2(1)).toBe(true); + expect(or3(2)).toBe(true); + expect(or4(3)).toBe(true); + expect(or5(4)).toBe(true); + expect(or6(5)).toBe(true); + expect(or7(6)).toBe(true); + expect(or8(7)).toBe(true); + expect(or9(8)).toBe(true); + expect(or10(9)).toBe(true); + expect(or11(10)).toBe(true); + expect(or11(11)).toBe(false); + }); + + test(`linearity (noneof)`, () => { + const noneof0 = Predicate.none(); + const noneof1 = Predicate.none(Num.gt(10)); + const noneof2 = Predicate.none(Num.gt(10), Num.lt(0)); + const noneof3 = Predicate.none(Num.gt(10), Num.lt(0), Num.lt(-9)); + const noneof4 = Predicate.none(Num.gt(10), Num.lt(0), Num.lt(-9), Num.gt(9)); + const noneof5 = Predicate.none(Num.gt(10), Num.lt(0), Num.lt(-9), Num.gt(9), Num.gt(8)); + const noneof6 = Predicate.none(Num.gt(10), Num.lt(0), Num.lt(-9), Num.gt(9), Num.gt(8), Num.gt(7)); + const noneof7 = Predicate.none(Num.gt(10), Num.lt(0), Num.lt(-9), Num.gt(9), Num.gt(8), Num.gt(7), Num.gt(6)); + const noneof8 = Predicate.none(Num.gt(10), Num.lt(0), Num.lt(-9), Num.gt(9), Num.gt(8), Num.gt(7), Num.gt(6), Num.gt(5)); + const noneof9 = Predicate.none(Num.gt(10), Num.lt(0), Num.lt(-9), Num.gt(9), Num.gt(8), Num.gt(7), Num.gt(6), Num.gt(5), Num.gt(4)); + const noneof10 = Predicate.none(Num.gt(10), Num.lt(0), Num.lt(-9), Num.gt(9), Num.gt(8), Num.gt(7), Num.gt(6), Num.gt(5), Num.gt(4), Num.gt(3)); + const noneof11 = Predicate.none(Num.gt(10), Num.lt(0), Num.lt(-9), Num.gt(9), Num.gt(8), Num.gt(7), Num.gt(6), Num.gt(5), Num.gt(4), Num.gt(3), Num.gt(2)); + + expect(noneof0(0)).toBe(true); + expect(noneof1(1)).toBe(true); + expect(noneof2(1)).toBe(true); + expect(noneof3(1)).toBe(true); + expect(noneof4(1)).toBe(true); + expect(noneof5(1)).toBe(true); + expect(noneof6(1)).toBe(true); + expect(noneof7(1)).toBe(true); + expect(noneof8(1)).toBe(true); + expect(noneof9(1)).toBe(true); + expect(noneof10(1)).toBe(true); + expect(noneof11(1)).toBe(true); + }); +}); \ No newline at end of file diff --git a/src/Result.test.ts b/src/Result.test.ts new file mode 100644 index 0000000..b2bda6c --- /dev/null +++ b/src/Result.test.ts @@ -0,0 +1,126 @@ +import { describe, expect, test } from 'vitest'; +import * as Result from './Result.js'; +import * as Num from './Num.js'; +import * as Str from './Str.js'; + +describe("Result", () => { + test(`checks if is ok or err`, () => { + expect(Result.isOk(10)).toBe(true); + expect(Result.isOk(Error)).toBe(true); + expect(Result.isOk(new Error())).toBe(false); + expect(Result.isErr(new Error())).toBe(true); + expect(Result.isErr(Error)).toBe(false); + expect(Result.isErr(null)).toBe(false); + expect(Result.isErr({ name: 'foo', message: 'bar', cause: 'baz' })).toBe(true); + }); + + test(Result.err.name, () => { + expect(Result.err(10) instanceof Error).toEqual(true); + expect(Result.err(new TypeError('aaaa')) instanceof Error).toEqual(true); + expect(Result.err({}) instanceof Error).toEqual(true); + expect(Result.err(10n) instanceof Error).toEqual(true); + expect(Result.err([10, 20, 30]) instanceof Error).toEqual(true); + }); + + test(Result.isErr.name, () => { + expect(Result.isErr(10)).toEqual(false); + expect(Result.isErr(new TypeError('aaaa'))).toEqual(true); + }); + + test(Result.isOk.name, () => { + expect(Result.isOk(10)).toEqual(true); + expect(Result.isOk(new TypeError('aaaa'))).toEqual(false); + }); + + test(Result.isOkOf.name, () => { + const guard = Result.isOkOf(Num.guard); + + expect(guard(10)).toEqual(true); + expect(guard("hello")).toEqual(false); + expect(guard(new TypeError('aaaa'))).toEqual(false); + }); + + test(Result.cmp.name, () => { + const cmp = Result.cmp(Str.cmp); + + expect(cmp("a", "a")).toEqual(0); + expect(cmp("a", "b")).toEqual(-1); + expect(cmp("b", "a")).toEqual(1); + expect(cmp(new Error(), new Error())).toEqual(0); + expect(cmp(new Error(), "a")).toEqual(-1); + expect(cmp("a", new Error())).toEqual(1); + }); + + test(Result.eq.name, () => { + const eq = Result.eq(Num.eq); + + expect(eq(0, 0)).toEqual(true); + expect(eq(new Error(), new TypeError())).toEqual(true); + expect(eq(new Error(), 0)).toEqual(false); + expect(eq(0, new Error())).toEqual(false); + expect(eq(1_000_000, 0)).toEqual(false); + }); + + test(Result.filter.name, () => { + const f = Result.filter(Num.gt(1)); + + expect(f(1)).toEqual(Error("Value did not pass filter")); + expect(f(2)).toEqual(2); + expect(Result.filter(Num.eq(0), 0)).toEqual(0); + expect(Result.filter(Num.eq(0), 1)).toEqual(Error("Value did not pass filter")); + expect(Result.filter(Num.eq(0), new TypeError())).toEqual(Error("Value did not pass filter")); + // expect(Result.filter(Num.gt(1), 2)).toEqual(2); + // expect(Result.filter(Num.gt(1), new TypeError())).toEqual(Error("Value did not pass filter")); + expect(f(new TypeError())).toEqual(Error("Value did not pass filter")); + }); + + test(Result.map.name, () => { + const m = Result.map(Num.add(10)); + + expect(m(10)).toEqual(20); + expect(m(new Error('foobar!'))).toEqual(Error('foobar!')); + }); + + test(Result.mapOr.name, () => { + const map = Result.mapOr(Str.length, 0); + + expect(map('hello')).toEqual(5); + expect(map(new Error())).toEqual(0); + }); + + test(Result.tryAsync.name, async () => { + const fn = async (arg: number) => { + if (Num.isEven(arg)) { + return arg * 2; + } + + throw new Error(`${arg} is not even`); + }; + + const safe = Result.tryAsync(fn); + + expect(await safe(2)).toEqual(4); + expect(await safe(3)).toEqual(Error("3 is not even")); + + expect(Result.isOk(await safe(2))).toEqual(true); + expect(Result.isErr(await safe(3))).toEqual(true); + }); + + test(Result.trySync.name, () => { + const fn = (arg: number) => { + if (Num.isEven(arg)) { + return arg * 2; + } + + throw new Error(`${arg} is not even`); + }; + + const safe = Result.trySync(fn); + + expect(safe(2)).toEqual(4); + expect(safe(3)).toEqual(Error("3 is not even")); + + expect(Result.isOk(safe(2))).toEqual(true); + expect(Result.isErr(safe(3))).toEqual(true); + }); +}); \ No newline at end of file diff --git a/src/Sequence.test.ts b/src/Sequence.test.ts new file mode 100644 index 0000000..9da7ad4 --- /dev/null +++ b/src/Sequence.test.ts @@ -0,0 +1,201 @@ +import { describe, expect, test } from 'vitest'; +import * as Num from './Num'; +import * as Sequence from './Sequence'; +import * as Str from './Str'; + +describe("Sequence", () => { + test(Sequence.make.name, () => { + const s = Sequence.make(10, 20, 30); + + expect(Sequence.guardOf(Num.guard)(s)).toEqual(true); + expect(Sequence.toArray(s)).toEqual([10, 20, 30]); + }); + + test(Sequence.guard.name, () => { + expect(Sequence.guard(10)).toEqual(false); + expect(Sequence.guard([10, 20, 30])).toEqual(false); + expect(Sequence.guard(Sequence.make())).toEqual(true); + }); + + test(Sequence.guardOf.name, () => { + const s0 = Sequence.make(1, 'hello', 2); + const s1 = Sequence.make('hello', 'world'); + const isStrSequence = Sequence.guardOf(Str.guard); + + expect(isStrSequence(s0)).toEqual(false); + expect(isStrSequence(s1)).toEqual(true); + }); + + test(Sequence.cmp.name, () => { + const s0 = Sequence.make(0, 1, 2); + const s1 = Sequence.make(0, 1, 2); + const s2 = Sequence.make(0, 1, 2, 3); + + expect(Sequence.cmp(Num, s0, s1)).toEqual(0); + expect(Sequence.cmp(Num, s0)(s1)).toEqual(0); + expect(Sequence.cmp(Num)(s0, s1)).toEqual(0); + + expect(Sequence.cmp(Num, s0, s2)).toEqual(-1); + expect(Sequence.cmp(Num, s0)(s2)).toEqual(-1); + expect(Sequence.cmp(Num)(s0, s2)).toEqual(-1); + + expect(Sequence.cmp(Num, s2, s0)).toEqual(1); + expect(Sequence.cmp(Num, s2)(s0)).toEqual(1); + expect(Sequence.cmp(Num)(s2, s0)).toEqual(1); + }); + + test(Sequence.eq.name, () => { + const s0 = Sequence.make(0, 1, 2); + const s1 = Sequence.make(0, 1, 2); + const s2 = Sequence.make(0, 1, 2, 3); + + expect(Sequence.eq(Num, s0, s1)).toEqual(true); + expect(Sequence.eq(Num, s0)(s1)).toEqual(true); + expect(Sequence.eq(Num)(s0, s1)).toEqual(true); + + expect(Sequence.eq(Num, s0, s2)).toEqual(false); + expect(Sequence.eq(Num, s0)(s2)).toEqual(false); + expect(Sequence.eq(Num)(s0, s2)).toEqual(false); + }); + + test(Sequence.map.name, () => { + const s = Sequence.make(1, 2, 3); + + expect(Sequence.map(s, Num.mul(2))).toEqual(Sequence.make(2, 4, 6)); + expect(Sequence.map(Num.mul(2))(s)).toEqual(Sequence.make(2, 4, 6)); + }); + + test(Sequence.filter.name, () => { + const s = Sequence.make(10, 20, 30, 40); + + expect(Sequence.filter(s, Num.gt(20))).toEqual(Sequence.make(30, 40)); + expect(Sequence.filter(Num.gt(20))(s)).toEqual(Sequence.make(30, 40)); + }); + + test(Sequence.append.name, () => { + const s0 = Sequence.make(10, 20); + + expect(Sequence.toArray(Sequence.append(s0, 30))).toEqual(Sequence.toArray(Sequence.make(10, 20, 30))); + expect(Sequence.toArray(Sequence.append(30)(s0))).toEqual(Sequence.toArray(Sequence.make(10, 20, 30))); + }); + + test(Sequence.concat.name, () => { + const s0 = Sequence.make(10, 20); + const s1 = Sequence.make(30, 40); + + expect(Sequence.concat(s0, s1)).toEqual(Sequence.make(10, 20, 30, 40)); + expect(Sequence.concat(s1)(s0)).toEqual(Sequence.make(10, 20, 30, 40)); + }); + + test(Sequence.prepend.name, () => { + const s0 = Sequence.make(10, 20); + + expect(Sequence.prepend(s0, 30)).toEqual(Sequence.make(30, 10, 20)); + expect(Sequence.prepend(30)(s0)).toEqual(Sequence.make(30, 10, 20)); + }); + + test(Sequence.first.name, () => { + const s0 = Sequence.make(10, 20, 30); + const s1 = Sequence.make(); + + expect(Sequence.first(s0)).toEqual(10); + expect(Sequence.first(s1)).toEqual(null); + }); + + test(Sequence.count.name, () => { + const s = Sequence.make(10, 20, 30); + + expect(Sequence.count(s, Num.gt(10))).toEqual(2); + expect(Sequence.count(Num.gt(10))(s)).toEqual(2); + }); + + test(Sequence.get.name, () => { + const s = Sequence.make('hello', 'world'); + + expect(Sequence.get(s, 0)).toEqual('hello'); + expect(Sequence.get(0)(s)).toEqual('hello'); + expect(Sequence.get(s, 9)).toEqual(new RangeError("Index out of bounds 9 for length 2")); + expect(Sequence.get(9)(s)).toEqual(new RangeError("Index out of bounds 9 for length 2")); + }); + + test(Sequence.last.name, () => { + const s0 = Sequence.make(10, 20, 30); + const s1 = Sequence.make(); + + expect(Sequence.last(s0)).toEqual(30); + expect(Sequence.last(s1)).toEqual(null); + }); + + test(Sequence.length.name, () => { + const s = Sequence.make(1, 2, 3); + + expect(Sequence.length(s)).toEqual(3); + }); + + test(Sequence.values.name, () => { + const s = Sequence.make('hello', 'world'); + + expect(Sequence.values(s)).toEqual({ 0: 'hello', 1: 'world' }); + }); + + test(Sequence.empty.name, () => { + const s = Sequence.make(); + const s1 = Sequence.make(10); + + expect(Sequence.empty(s)).toEqual(true); + expect(Sequence.empty(s1)).toEqual(false); + }); + + test(Sequence.populated.name, () => { + const s = Sequence.make(10, 20, 30); + + expect(Sequence.populated(s)).toEqual(true); + expect(Sequence.populated(Sequence.make())).toEqual(false); + }); + + test(Sequence.fromString.name, () => { + const x = Sequence.fromString(`10,20,30`); + const t1 = Sequence.make(10, 20, 30); + const t2 = Sequence.make(10, 30, 20); + + expect(Sequence.eq(Num, x as any, t2)).toEqual(false); + expect(Sequence.eq(Num, x as any, t1)).toEqual(true); + expect(Sequence.fromString(`asd`)).toBeInstanceOf(Error); + }); + + test(Sequence.sort.name, () => { + const a = Sequence.make(5, 3, 1, 4, 2); + const b = Sequence.sort(a, Num); + + expect(b).toEqual(Sequence.make(1, 2, 3, 4, 5)); + expect(Sequence.sort(Num)(a)).toEqual(Sequence.make(1, 2, 3, 4, 5)); + expect(Sequence.sort(Num.cmp)(a)).toEqual(Sequence.make(1, 2, 3, 4, 5)); + }); + + test(Sequence.toArray.name, () => { + const s = Sequence.make(10, 20, 30); + + expect(Sequence.toArray(s)).toEqual([10, 20, 30]); + }); + + test(Sequence.toJSON.name, () => { + expect(Sequence.toJSON(Sequence.make(1, 2))).toEqual([1, 2]); + }); + + test(Sequence.toMap.name, () => { + const c = new Map(); + + c.set("0", 1); + c.set("1", 2); + + expect(Sequence.toMap(Sequence.make(1, 2))).toEqual(c); + }); + + test(Sequence.toSet.name, () => { + expect(Sequence.toSet(Sequence.make(10, 20, 30))).toEqual(new Set([10, 20, 30])); + }); + + test(Sequence.toString.name, () => { + expect(Sequence.toString(Sequence.make(10, 20, 30))).toEqual('10,20,30'); + }); +}); diff --git a/src/Sequence.ts b/src/Sequence.ts new file mode 100644 index 0000000..7cdbd6a --- /dev/null +++ b/src/Sequence.ts @@ -0,0 +1,678 @@ +import { cmp as arrCmp, eq as arrEq } from './Arr.js'; +import type * as Fn from './Fn.js'; +import type * as Functors from './Functors.js'; +import type * as Option from './Option.js'; +import type * as Result from './Result.js'; +import { isErr } from './Result.js'; + +const indexer = Symbol("indexer"); + +/** + * A `Sequence.t` is an iterable list of elements `a` in a particular order. + * + * It's immutable by design. + */ +export type t = Iterable & { + [indexer](): Readonly>; + [Symbol.iterator](): Iterator; +}; + +//#region factories + +/** + * Makes a new `Sequence.t` from a list of arguments `a`. + * + * @example + * + * ```ts + * import { Sequence } from 'tiinvo' + * + * Sequence.make(10, 20, 30) // Sequence.t + * ``` + * + * @since 4.0.0 + */ +export const make = (...v: a[]): t => { + const values: Record = {}; + + for (let i = 0; i < v.length; i++) { + values[i] = v[i]; + } + + const _t = { + [indexer]() { + return Object.freeze({ ...values }); + }, + } as t; + + Object.defineProperty(_t, Symbol.iterator, { + enumerable: false, + writable: false, + configurable: true, + value: function () { + let i = 0; + let keys = Object.keys(values) as unknown as number[]; + + return { + next: () => { + return { + value: values[keys[i++]], + done: (i > keys.length) + }; + } + }; + } + }); + + return _t; +}; + +//#endregion + +//#region guardables + +/** + * Checks if a value `x` is a `Sequence.t`. + * + * @example + * + * ```ts + * import { Sequence } from 'tiinvo' + * + * Sequence.guard(10) // false + * Sequence.guard([10, 20, 30]) // false + * Sequence.guard(Sequence.make()) // true + * ``` + * + * @since 4.0.0 + */ +export const guard = (x: unknown): x is t => typeof x === 'object' && x !== null && indexer in x; + +/** + * Checks if the parameter `x` is a `Sequence.t` + * + * @example + * + * ```ts + * import { Sequence, Num, Str } from 'tiinvo' + * + * const s0 = Sequence.make(1, 'hello', 2) + * const s1 = Sequence.make('hello', 'world') + * const isStrSequence = Sequence.guardOf(Str.guard); + * + * isStrSequence(s0) // false + * isStrSequence(s1) // true + * ``` + * + * @since 4.0.0 + */ +export const guardOf = (g: Functors.Guardable) => (x: unknown): x is t => guard(x) && toArray(x).every(g); + +//#endregion + +//#region comparables + +/** + * Returns 0 if two sequences are the same, -1 if a is less than b or 1 if a is more than b + * + * @example + * + * ```ts + * import { Sequence, Num } from 'tiinvo' + * + * const s0 = Sequence.make(0, 1, 2) + * const s1 = Sequence.make(0, 1, 2) + * const s2 = Sequence.make(0, 1, 2, 3) + * + * Sequence.cmp(Num, s0, s1) // 0 + * Sequence.cmp(Num, s0)(s1) // 0 + * Sequence.cmp(Num)(s0, s1) // 0 + * + * Sequence.cmp(Num, s0, s2) // -1 + * Sequence.cmp(Num, s0)(s2) // -1 + * Sequence.cmp(Num)(s0, s2) // -1 + * + * Sequence.cmp(Num, s2, s0) // 1 + * Sequence.cmp(Num, s2)(s0) // 1 + * Sequence.cmp(Num)(s2, s0) // 1 + * ``` + * + * @since 4.0.0 + */ +export function cmp(mod: Functors.ComparableModule, a: t, b: t): Functors.ComparableResult; +export function cmp(mod: Functors.ComparableModule, a: t): Fn.Unary, Functors.ComparableResult>; +export function cmp(mod: Functors.ComparableModule): Fn.Binary, t, Functors.ComparableResult>; +export function cmp(mod: Functors.ComparableModule, a?: t, b?: t): any { + const c = arrCmp(mod.cmp); + + if (guard(a) && guard(b)) { + return c(toArray(a), toArray(b)); + } else if (guard(a) && !guard(b)) { + return (d: t) => c(toArray(a), toArray(d)); + } else { + return (d: t, e: t) => c(toArray(d), toArray(e)); + } +} + +/** + * Returns true if two sequences are the same + * + * @example + * + * ```ts + * import { Sequence, Num } from 'tiinvo' + * + * const s0 = Sequence.make(0, 1, 2) + * const s1 = Sequence.make(0, 1, 2) + * const s2 = Sequence.make(0, 1, 2, 3) + * + * Sequence.eq(Num, s0, s1) // true + * Sequence.eq(Num, s0)(s1) // true + * Sequence.eq(Num)(s0, s1) // true + * + * Sequence.eq(Num, s0, s2) // false + * Sequence.eq(Num, s0)(s2) // false + * Sequence.eq(Num)(s0, s2) // false + * ``` + * + * @since 4.0.0 + */ +export function eq(mod: Functors.EquatableModule, a: t, b: t): boolean; +export function eq(mod: Functors.EquatableModule, a: t): Fn.Unary, boolean>; +export function eq(mod: Functors.EquatableModule): Fn.Binary, t, boolean>; +export function eq(mod: Functors.EquatableModule, a?: t, b?: t): any { + const eqfn = arrEq(mod.eq); + + if (guard(a) && guard(b)) { + return eqfn(toArray(a), toArray(b)); + } else if (guard(a) && !guard(b)) { + return (d: t) => eqfn(toArray(a), toArray(d)); + } else { + return (d: t, e: t) => eqfn(toArray(d), toArray(e)); + } +} + +//#endregion + +//#region mappables + +/** + * Maps a `Sequence.t` to a `Sequence.t` using the functor `Functors.Mappable`. + * + * @example + * + * ```ts + * import { Sequence, Num } from 'tiinvo' + * + * const s = Sequence.make(1, 2, 3) + * + * Sequence.map(s, Num.mul(2)) // Sequence.t(2, 4, 6) + * Sequence.map(Num.mul(2))(s) // Sequence.t(2, 4, 6) + * ``` + * + * @since 4.0.0 + */ +export function map(a: t, m: Functors.Mappable): t; +export function map(a: Functors.Mappable): Fn.Unary, t>; +export function map(a: t | Functors.Mappable, m?: any): any { + if (guard(a) && typeof m === 'function') { + return make.apply(null, toArray(a).map(m)); + } + + return (b: t) => { + return make.apply(null, toArray(b).map(a as Functors.Mappable)); + }; +} + +//#endregion + +//#region filterables + +/** + * Filters a `Sequence.t` with a specified `Filterable` + * + * @example + * + * ```ts + * import { Sequence, Num } from 'tiinvo' + * + * const s = Sequence.make(10, 20, 30, 40) + * + * Sequence.filter(s, Num.gt(20)) // Sequence.make(30, 40) + * Sequence.filter(Num.gt(20))(s) // Sequence.make(30, 40) + * ``` + * + * @since 4.0.0 + */ +export function filter(a: t, f: Functors.Filterable): t; +export function filter(a: Functors.Filterable): Fn.Unary, t>; +export function filter(a: t | Functors.Filterable, f?: Functors.Filterable): any { + if (guard(a) && typeof f === 'function') { + return make.apply(null, Array.from(a).filter(f)); + } + + return (c: t) => make.apply(null, Array.from(c).filter(a as Functors.Filterable)); +} + +//#endregion + +//#region operators + +/** + * Adds an element to the end of the `Sequence.t` without mutating the original one. + * + * @example + * + * ```ts + * import { Sequence } from 'tiinvo' + * + * const s0 = Sequence.make(10, 20) + * + * Sequence.append(s0, 30) // Sequence(10, 20, 30) + * Sequence.append(30)(s0) // Sequence(10, 20, 30) + * ``` + * + * @since 4.0.0 + */ +export function append(a: t, b: a): t; +export function append(a: a): Fn.Unary, t>; +export function append(a: any, b?: any): any { + if (guard(a)) { + return make.apply(null, Object.values(a[indexer]()).concat(b)); + } + + return (b: t) => make.apply(null, Object.values(b[indexer]()).concat(a)); +} + +/** + * Concatenates two `Sequence.t` and `Sequence.t` and return a new `Sequence.t`. + * + * @example + * + * ```ts + * import { Sequence } from 'tiinvo' + * + * const s0 = Sequence.make(10, 20) + * const s1 = Sequence.make(30, 40) + * + * Sequence.concat(s0, s1) // Sequence(10, 20, 30, 40) + * Sequence.concat(s1)(s0) // Sequence(10, 20, 30, 40) + * ``` + * + * @since 4.0.0 + */ +export function concat(a: t, b: t): t; +export function concat(a: t): Fn.Unary, t>; +export function concat(a: t, b?: t): any { + if (guard(a) && guard(b)) { + return make.apply(null, [...a, ...b]); + } + + return (b: t) => make.apply(null, [...b, ...a]); +} + +/** + * Adds an element to the start of the `Sequence.t` without mutating the original one. + * + * @example + * + * ```ts + * import { Sequence } from 'tiinvo' + * + * const s0 = Sequence.make(10, 20) + * + * Sequence.prepend(s0, 30) // Sequence(30, 10, 20) + * Sequence.prepend(30)(s0) // Sequence(30, 10, 20) + * ``` + * + * @since 4.0.0 + */ +export function prepend(a: t, b: a): t; +export function prepend(a: a): Fn.Unary, t>; +export function prepend(a: any, b?: any): any { + if (guard(a)) { + return make.apply(null, [b].concat([...a])); + } + + return (b: t) => make.apply(null, [a].concat([...b])); +} + +//#endregion + +//#region getters + +/** + * Counts the number of elements that satisfy a given predicate + * + * @example + * + * ```ts + * import { Sequence, Num } from 'tiinvo' + * + * const s = Sequence.make(10, 20, 30) + * + * Sequence.count(s, Num.gt(10)) // 2 + * Sequence.count(Num.gt(10))(s) // 2 + * ``` + * + * @since 4.0.0 + */ +export function count(a: t, p: Functors.Filterable): number; +export function count(a: Functors.Filterable): Fn.Unary, number>; +export function count(a: t | Functors.Filterable, p?: Functors.Filterable): any { + if (guard(a) && typeof p === 'function') { + return length(filter(a, p)); + } + + return (c: t) => length(filter(c, a as Functors.Filterable)); +} + +/** + * Gets a `Sequence.t`'s first element. + * + * Returns `Option.None` if none is found. + * + * @example + * + * ```ts + * import { Sequence } from 'tiinvo' + * + * const s0 = Sequence.make(10, 20, 30) + * const s1 = Sequence.make() + * + * Sequence.first(s0) // 10 + * Sequence.first(s1) // null + * ``` + * + * @since 4.0.0 + */ +export const first = (a: t): Option.t => { + const f = get(a, 0); + return isErr(f) ? null : f as unknown as Option.t; +}; + +/** + * Gets an element at a specific index. + * + * If the index is out of bounds, `Option.None` is returned, otherwise it returns `a` + * + * @example + * + * ```ts + * import { Sequence } from 'tiinvo' + * + * const s = Sequence.make('hello', 'world') + * + * Sequence.get(s, 0) // 'hello' + * Sequence.get(s, 9) // null + * ``` + * + * @since 4.0.0 + */ +export function get(a: t, i: number): Result.t; +export function get(a: number): Fn.Unary, Result.t>; +export function get(a: any, i?: any): any { + if (guard(a)) { + const s = length(a); + + if (i < 0 || i > s - 1) { + return new RangeError(`Index out of bounds ${i} for length ${s}`); + } + + return values(a)[i]; + } + + return (b: t) => { + const s = length(b); + + if (a < 0 || a > s - 1) { + return new RangeError(`Index out of bounds ${a} for length ${s}`); + } + + return values(b)[a]; + }; +} + +/** + * Gets a `Sequence.t`'s last element. + * + * Returns `Option.None` if none is found. + * + * @example + * + * ```ts + * import { Sequence } from 'tiinvo' + * + * const s0 = Sequence.make(10, 20, 30) + * const s1 = Sequence.make() + * + * Sequence.last(s0) // 30 + * Sequence.last(s1) // null + * ``` + * + * @since 4.0.0 + */ +export const last = (a: t): Option.t => { + const f = get(a, length(a) - 1); + return isErr(f) ? null : f as any; +}; + +/** + * Gets the length of a `Sequence.t` + * + * @example + * + * ```ts + * import { Sequence } from 'tiinvo' + * + * const s = Sequence.make(1, 2, 3) + * + * Sequence.length(s) // 3 + * ``` + * + * @since 4.0.0 + */ +export const length = (t: t): number => Object.values(t[indexer]()).length; + +/** + * Gets values of a `Sequence.t` as an immutable indexed object. + * + * @example + * + * ```ts + * import { Sequence } from 'tiinvo' + * + * const s = Sequence.make('hello', 'world') + * + * Sequence.values(s) // { 0: 'hello', 1: 'world' } + * ``` + * + * @since 4.0.0 + */ +export const values = (t: t) => t[indexer](); + +//#endregion + +//#region predicates + +/** + * Returns `true` if the sequence is empty. + * + * @example + * + * ```ts + * import { Sequence } from 'tiinvo' + * + * const s = Sequence.make() + * const s1 = Sequence.make(10) + * + * Sequence.empty(s) // true + * Sequence.empty(s1) // false + * ``` + * + * @since 4.0.0 + */ +export const empty = (t: t) => length(t) === 0; + +/** + * Returns `true` if the sequence is populated. + * + * @example + * + * ```ts + * import { Sequence } from 'tiinvo' + * + * const s = Sequence.make(10, 20, 30) + * + * Sequence.populated(s) // true + * Sequence.populated(Sequence.make()) // false + * ``` + * + * @since 4.0.0 + */ +export const populated = (t: t) => length(t) !== 0; + +//#endregion + +//#region sortables + +/** + * Sorts and returns a new `Sequence.t` values with a `Comparable` or `ComparableModule` functor. + * + * @example + * + * ```ts + * import { Sequence, Num } from 'tiinvo' + * + * const a = Sequence.make(5, 3, 1, 4, 2) + * const b = Sequence.sort(a, Num) + * + * b // Sequence.make(1, 2, 3, 4, 5) + * ``` + * + * @since 4.0.0 + */ +export function sort(a: t, mod: Functors.Comparable | Functors.ComparableModule): t; +export function sort(a: Functors.Comparable | Functors.ComparableModule): Fn.Unary, t>; +export function sort(a: t | Functors.Comparable | Functors.ComparableModule, mod?: Functors.Comparable | Functors.ComparableModule): any { + const getcmp = (x: Functors.Comparable | Functors.ComparableModule) => typeof x === 'function' ? x : x.cmp; + + if (arguments.length === 2 && guard(a)) { + return make.apply(null, Array.from(a).sort(getcmp(mod!))); + } + + return (x: t) => make.apply(null, Array.from(x).sort(getcmp(a as Functors.Comparable | Functors.ComparableModule))); +} + +//#endregion + +//#region serializables + +/** + * Try to convert a `string` to a `Sequence.t` + * + * @example + * + * ```ts + * import { Sequence } from 'tiinvo' + * + * const x = Sequence.fromString(`10,20,30`) + * const t1 = Sequence.make(10, 20, 30) + * const t2 = Sequence.make(10, 30, 20) + * + * Sequence.eq(x as any, t1) // true + * Sequence.eq(x as any, t2) // false + * ``` + * + * @since 4.0.0 + */ +export const fromString = (x: string): Result.t> => { + try { + return make.apply(null, JSON.parse(`[${x}]`)) as t; + } catch (err) { + return err as Error; + } +}; + +/** + * Converts a `Sequence.t` to an array `a[]` + * + * @example + * + * ```ts + * import { Sequence } from 'tiinvo' + * + * const s = Sequence.make(10, 20, 30) + * + * Sequence.toArray(s) // [10, 20, 30] + * ``` + * + * @since 4.0.0 + */ +export const toArray = (a: t): a[] => Array.from(a); + +/** + * Serializes a `Sequence.t` to json + * + * @example + * + * ```ts + * import { Sequence } from 'tiinvo' + * + * Sequence.toJSON(Sequence.make(1, 2)) // { 0: 1, 1: 2 } + * ``` + * + * @since 4.0.0 + */ +export const toJSON = (a: t) => Array.from(a); + +/** + * Serializes a `Sequence.t` to a `Map` + * + * @example + * + * ```ts + * import { Sequence } from 'tiinvo' + * + * Sequence.toMap(Sequence.make(1, 2)) // Map([0, 1], [1, 2]) + * ``` + * + * @since 4.0.0 + */ +export const toMap = (a: t) => new Map(Object.entries(a[indexer]())); + +/** + * Converts a `Sequence.t` to a Set `Set` + * + * @example + * + * ```ts + * import { Sequence } from 'tiinvo' + * + * const s = Sequence.make(10, 20, 30) + * + * Sequence.toSet(s) // Set(10, 20, 30) + * ``` + * + * @since 4.0.0 + */ +export const toSet = (a: t): Set => new Set(a); + +/** + * Stringifies a `Sequence.t` + * + * @example + * + * ```ts + * import { Sequence } from 'tiinvo' + * + * const s = Sequence.make(10, 20, 30) + * + * Sequence.toString(s) // '10,20,30' + * ``` + * + * @since 4.0.0 + */ +export const toString = (a: t) => Array.from(a).toString(); + +//#endregion \ No newline at end of file diff --git a/src/SortedSequence.test.ts b/src/SortedSequence.test.ts new file mode 100644 index 0000000..74e7d53 --- /dev/null +++ b/src/SortedSequence.test.ts @@ -0,0 +1,176 @@ +import { describe, expect, test } from 'vitest'; +import * as SortedSequence from './SortedSequence.js'; +import * as Num from './Num.js'; +import * as Str from './Str.js'; + +describe(`SortedSequence`, () => { + test(SortedSequence.make.name, () => { + const s0 = SortedSequence.make(Num, 10, 20, 30); + const s1 = SortedSequence.make(Str, 'hello', 'world'); + const s2 = SortedSequence.make(Str.cmp, 'hello', 'world'); + + expect(SortedSequence.guardOf(Num.guard)(s0)).toEqual(true); + expect(SortedSequence.guardOf(Num.guard)(s1)).toEqual(false); + expect(SortedSequence.guardOf(Str.guard)(s0)).toEqual(false); + expect(SortedSequence.guardOf(Str.guard)(s1)).toEqual(true); + expect(SortedSequence.guardOf(Str.guard)(s2)).toEqual(true); + }); + + test(SortedSequence.guard.name, () => { + const s = SortedSequence.make(Str); + + expect(SortedSequence.guard(s)).toEqual(true); + expect(SortedSequence.guard([])).toEqual(false); + }); + + test(SortedSequence.guardOf.name, () => { + const s0 = SortedSequence.make(Num, 1, 2); + const s1 = SortedSequence.make(Str, 'hello', 'world'); + const isStrSortedList = SortedSequence.guardOf(Str.guard); + + expect(isStrSortedList(s0)).toEqual(false); + expect(isStrSortedList(s1)).toEqual(true); + }); + + test(SortedSequence.cmp.name, () => { + const s0 = SortedSequence.make(Num, 0, 1, 2); + const s1 = SortedSequence.make(Num, 0, 1, 2); + const s2 = SortedSequence.make(Num, 0, 1, 2, 3); + + expect(SortedSequence.cmp(s0, s1)).toEqual(0); + expect(SortedSequence.cmp(s0)(s1)).toEqual(0); + + expect(SortedSequence.cmp(s0, s2)).toEqual(-1); + expect(SortedSequence.cmp(s0)(s2)).toEqual(-1); + + expect(SortedSequence.cmp(s2, s0)).toEqual(1); + expect(SortedSequence.cmp(s2)(s0)).toEqual(1); + }); + + test(SortedSequence.eq.name, () => { + const s0 = SortedSequence.make(Num, 0, 1, 2); + const s1 = SortedSequence.make(Num, 0, 1, 2); + const s2 = SortedSequence.make(Num, 0, 1, 2, 3); + + expect(SortedSequence.eq(s0, s1)).toEqual(true); + expect(SortedSequence.eq(s0)(s1)).toEqual(true); + + expect(SortedSequence.eq(s0, s2)).toEqual(false); + expect(SortedSequence.eq(s0)(s2)).toEqual(false); + }); + + test(SortedSequence.map.name, () => { + const s = SortedSequence.make(Num, 3, 1, 2); + const m = Num.mul(2); + + expect(SortedSequence.map(s, m)).toEqual(SortedSequence.make(Num, 2, 4, 6)); + expect(SortedSequence.map(m)(s)).toEqual(SortedSequence.make(Num, 2, 4, 6)); + }); + + test(SortedSequence.add.name, () => { + const s0 = SortedSequence.make(Num, 10, 20); + + expect(SortedSequence.toArray(SortedSequence.add(s0, 30))).toEqual(SortedSequence.toArray(SortedSequence.make(Num, 10, 20, 30))); + expect(SortedSequence.toArray(SortedSequence.add(30)(s0))).toEqual(SortedSequence.toArray(SortedSequence.make(Num, 10, 20, 30))); + }); + + test(SortedSequence.concat.name, () => { + const s0 = SortedSequence.make(Num, 10, 20); + const s1 = SortedSequence.make(Num, 30, 40); + + expect(SortedSequence.concat(s0, s1)).toEqual(SortedSequence.make(Num, 10, 20, 30, 40)); + expect(SortedSequence.concat(s1)(s0)).toEqual(SortedSequence.make(Num, 10, 20, 30, 40)); + }); + + test(SortedSequence.count.name, () => { + const s = SortedSequence.make(Num, 10, 20, 30); + + expect(SortedSequence.count(s, Num.gt(10))).toEqual(2); + expect(SortedSequence.count(Num.gt(10))(s)).toEqual(2); + }); + + test(SortedSequence.get.name, () => { + const s = SortedSequence.make(Str, 'hello', 'world'); + + expect(SortedSequence.get(s, 0)).toEqual('hello'); + expect(SortedSequence.get(s, 9)).toEqual(new RangeError("Index out of bounds 9 for length 2")); + }); + + test(SortedSequence.first.name, () => { + const s0 = SortedSequence.make(Num, 10, 20, 30); + const s1 = SortedSequence.make(Num); + + expect(SortedSequence.first(s0)).toEqual(10); + expect(SortedSequence.first(s1)).toEqual(null); + }); + + test(SortedSequence.last.name, () => { + const s0 = SortedSequence.make(Num, 10, 20, 30); + const s1 = SortedSequence.make(Num); + + expect(SortedSequence.last(s0)).toEqual(30); + expect(SortedSequence.last(s1)).toEqual(null); + }); + + test(SortedSequence.length.name, () => { + const s = SortedSequence.make(Num, 1, 2, 3); + + expect(SortedSequence.length(s)).toEqual(3); + }); + + test(SortedSequence.values.name, () => { + const s = SortedSequence.make(Str, 'hello', 'world'); + + expect(SortedSequence.values(s)).toEqual({ 0: 'hello', 1: 'world' }); + }); + + test(SortedSequence.empty.name, () => { + const s = SortedSequence.make(Num); + const s1 = SortedSequence.make(Num, 10); + + expect(SortedSequence.empty(s)).toEqual(true); + expect(SortedSequence.empty(s1)).toEqual(false); + }); + + test(SortedSequence.populated.name, () => { + const s = SortedSequence.make(Num, 10, 20, 30); + + expect(SortedSequence.populated(s)).toEqual(true); + expect(SortedSequence.populated(SortedSequence.make(Num))).toEqual(false); + }); + + test(SortedSequence.toArray.name, () => { + const sl = SortedSequence.make(Num, 3, 2, 1); + + expect(SortedSequence.toArray(sl)).toEqual([1, 2, 3]); + }); + + test(SortedSequence.toJSON.name, () => { + const sl = SortedSequence.make(Num, 3, 2, 1); + + expect(SortedSequence.toJSON(sl)).toEqual([1, 2, 3]); + }); + + test(SortedSequence.toMap.name, () => { + const sl = SortedSequence.make(Num, 3, 2, 1); + const m = new Map(); + + m.set("0", 1); + m.set("1", 2); + m.set("2", 3); + + expect(SortedSequence.toMap(sl)).toEqual(m); + }); + + test(SortedSequence.toSet.name, () => { + const sl = SortedSequence.make(Num, 3, 2, 1); + + expect(SortedSequence.toSet(sl)).toEqual(new Set([1, 2, 3])); + }); + + test(SortedSequence.toString.name, () => { + const sl = SortedSequence.make(Num, 3, 2, 1); + + expect(SortedSequence.toString(sl)).toEqual("1,2,3"); + }); +}); diff --git a/src/SortedSequence.ts b/src/SortedSequence.ts new file mode 100644 index 0000000..8d73a4d --- /dev/null +++ b/src/SortedSequence.ts @@ -0,0 +1,493 @@ +import type * as Fn from './Fn.js'; +import type * as Functors from './Functors.js'; +import * as Sequence from './Sequence.js'; + +const sortsymbol = Symbol('Sequence.sorted'); + +/** + * A sorted list is a `Sequence.t` which all elements stored in it are sorted by a `Comparable` functor. + */ +export type t = Sequence.t & { + [sortsymbol]: Functors.Comparable; +}; + +//#region factories + +/** + * Makes an immutable `SortedSequence.t` from a `ComparableModule` and (optionally) a list of arguments as initial values + * + * @example + * + * ```ts + * import { SortedSequence, Num, Str } from 'tiinvo' + * + * const s0 = SortedSequence.make(Num, 10, 20, 30) + * const s1 = SortedSequence.make(Str, 'hello', 'world') + * + * SortedSequence.guardOf(Num.guard)(s0) // true + * SortedSequence.guardOf(Num.guard)(s1) // false + * SortedSequence.guardOf(Str.guard)(s0) // false + * SortedSequence.guardOf(Str.guard)(s1) // true + * ``` + * + * @since 4.0.0 + */ +export function make(mod: Functors.Comparable, ...args: a[]): t +export function make(mod: Functors.ComparableModule, ...args: a[]): t +export function make(mod: Functors.Comparable | Functors.ComparableModule, ...args: a[]): t +{ + const cmp = typeof mod === 'function' ? mod : mod.cmp; + const seq = Sequence.make.apply(null, args.sort(cmp)) as t; + + seq[sortsymbol] = cmp; + + return seq; +}; + +//#endregion + +//#region guardables + +/** + * Checks if the parameter `x` is a `SortedSequence.t` + * + * @example + * + * ```ts + * import { SortedSequence, Str } from 'tiinvo' + * + * const s = SortedSequence.make(Str) + * + * SortedSequence.guard(s) // true + * SortedSequence.guard([]) // false + * ``` + * + * @since 4.0.0 + */ +export const guard = (x: unknown): x is t => Sequence.guard(x) && sortsymbol in x; + +/** + * Checks if the parameter `x` is a `SortedSequence.t` + * + * @example + * + * ```ts + * import { SortedSequence, Num, Str } from 'tiinvo' + * + * const s0 = SortedSequence.make(Num, 1, 2) + * const s1 = SortedSequence.make(Str, 'hello', 'world') + * const isStrSortedList = SortedSequence.guardOf(Str.guard); + * + * isStrSortedList(s0) // false + * isStrSortedList(s1) // true + * ``` + * + * @since 4.0.0 + */ +export const guardOf = (g: Functors.Guardable) => (x: unknown): x is t => guard(x) && toArray(x).every(g); + +//#endregion + +//#region comparables + +/** + * Returns 0 if two sorted lists are the same, -1 if a is less than b or 1 if a is more than b + * + * @example + * + * ```ts + * import { SortedSequence, Num } from 'tiinvo' + * + * const s0 = SortedSequence.make(Num, 0, 1, 2) + * const s1 = SortedSequence.make(Num, 0, 1, 2) + * const s2 = SortedSequence.make(Num, 0, 1, 2, 3) + * + * SortedSequence.cmp(s0, s1) // 0 + * SortedSequence.cmp(s0)(s1) // 0 + * + * SortedSequence.cmp(s0, s2) // -1 + * SortedSequence.cmp(s0)(s2) // -1 + * + * SortedSequence.cmp(s2, s0) // 1 + * SortedSequence.cmp(s2)(s0) // 1 + * ``` + * + * @since 4.0.0 + */ +export function cmp>(a: a, b: a): Functors.ComparableResult; +export function cmp>(a: a): Fn.Unary; +export function cmp>(a: a, b?: a): any { + if (guard(a) && guard(b)) { + return Sequence.cmp({ cmp: a[sortsymbol] }, a, b); + } + + return (c: a) => Sequence.cmp({ cmp: a[sortsymbol] }, a, c); +} + +/** + * Returns true if two sorted lists are the same + * + * @example + * + * ```ts + * import { SortedSequence, Num } from 'tiinvo' + * + * const s0 = SortedSequence.make(Num, 0, 1, 2) + * const s1 = SortedSequence.make(Num, 0, 1, 2) + * const s2 = SortedSequence.make(Num, 0, 1, 2, 3) + * + * SortedSequence.eq(s0, s1) // true + * SortedSequence.eq(s0)(s1) // true + * + * SortedSequence.eq(s0, s2) // false + * SortedSequence.eq(s0)(s2) // false + * ``` + * + * @since 4.0.0 + */ +export function eq>(a: a, b: a): boolean; +export function eq>(a: a): Fn.Unary; +export function eq>(a: a, b?: a): any { + if (guard(a) && guard(b)) { + return Sequence.cmp({ cmp: a[sortsymbol] }, a, b) === 0; + } + + return (b: a) => Sequence.cmp({ cmp: a[sortsymbol] }, a, b) === 0; +} + +//#endregion + +//#region mappables + +/** + * Maps a `SortedSequence.t` to a `SortedSequence.t` using the functor `Functors.Mappable`. + * + * @example + * + * ```ts + * import { SortedSequence, Num } from 'tiinvo' + * + * const s = SortedSequence.make(Num, 3, 1, 2) + * const m = Num.mul(2) + * + * SortedSequence.map(s, m) // SortedSequence.t(2, 4, 6) + * SortedSequence.map(m)(s) // SortedSequence.t(2, 4, 6) + * ``` + * + * @since 4.0.0 + */ +export function map(a: t, m: Functors.Mappable): t; +export function map(a: Functors.Mappable): Fn.Unary, t>; +export function map(a: t | Functors.Mappable, m?: any): any { + if (guard(a) && typeof m === 'function') { + return make.apply(null, [{ cmp: a[sortsymbol] }, ... [... a].map(m)]); + } + + return (c: t) => { + return make.apply(null, [{ cmp: c[sortsymbol] as any }, ... [... c].map(a as Functors.Mappable)]); + }; +} + +//#endregion + +//#region operators + +/** + * Adds an element to the end of the `Sequence.t` without mutating the original one. + * + * @example + * + * ```ts + * import { SortedSequence, Num } from 'tiinvo' + * + * const s0 = SortedSequence.make(Num, 10, 20) + * + * SortedSequence.add(s0, 30) // SortedSequence(10, 20, 30) + * SortedSequence.add(30)(s0) // SortedSequence(10, 20, 30) + * ``` + * + * @since 4.0.0 + */ +export function add, b>(a: a, b: b): t; +export function add, b>(a: b): Fn.Unary>; +export function add, b>(a: a | b, b?: b): any { + if (guard(a) && arguments.length === 2) { + return make.apply(null, [{ cmp: a[sortsymbol] }, ...a, b]); + } + + return (c: a) => make.apply(null, [{ cmp: c[sortsymbol] as any }, ...c, a]); +} + +/** + * Concatenates two `SortedSequence.t` and `SortedSequence.t` and return a new `SortedSequence.t`. + * + * @example + * + * ```ts + * import { SortedSequence, Num } from 'tiinvo' + * + * const s0 = SortedSequence.make(Num, 10, 20) + * const s1 = SortedSequence.make(Num, 30, 40) + * + * SortedSequence.concat(s0, s1) // SortedSequence(10, 20, 30, 40) + * SortedSequence.concat(s1)(s0) // SortedSequence(10, 20, 30, 40) + * ``` + * + * @since 4.0.0 + */ +export function concat(a: t, b: t): t; +export function concat(a: t): (x: t) => t; +export function concat(a: t | t, b?: t): any { + if (guard(a) && guard(b)) { + return make.apply(null, [{ cmp: a[sortsymbol] }, ...a, ...b!]); + } + + return (x: t) => make.apply(null, [{ cmp: x[sortsymbol] as any }, ...a, ...x]); +} + +//#endregion + +//#region getters + +/** + * Counts the number of elements that satisfy a given predicate + * + * @example + * + * ```ts + * import { SortedSequence, Num } from 'tiinvo' + * + * const s = SortedSequence.make(Num, 10, 20, 30) + * + * SortedSequence.count(s, Num.gt(10)) // 2 + * SortedSequence.count(Num.gt(10))(s) // 2 + * ``` + * + * @since 4.0.0 + */ +export const count = Sequence.count; + +/** + * + * + * @example + * + * ```ts + * import { SortedSequence, Num } from 'tiinvo' + * + * const s = SortedSequence.make(Num, 'hello', 'world') + * + * SortedSequence.get(s, 0) // 'hello' + * SortedSequence.get(s, 9) // null + * ``` + * + * @since 4.0.0 + */ +export const get = Sequence.get; + +/** + * + * + * @example + * + * ```ts + * import { SortedSequence, Num } from 'tiinvo' + * + * const s0 = SortedSequence.make(Num, 10, 20, 30) + * const s1 = SortedSequence.make(Num) + * + * SortedSequence.first(s0) // 10 + * SortedSequence.first(s1) // null + * ``` + * + * @since 4.0.0 + */ +export const first = Sequence.first; + +/** + * Gets a Sequence.t's last element. + * + * Returns Option.None if none is found. + * + * @example + * + * ```ts + * import { SortedSequence, Num } from 'tiinvo' + * + * const s0 = SortedSequence.make(Num, 10, 20, 30) + * const s1 = SortedSequence.make(Num) + * + * SortedSequence.last(s0) // 30 + * SortedSequence.last(s1) // null + * ``` + * ``` + * + * @since 4.0.0 + */ +export const last = Sequence.last; + +/** + * Gets the length of a `SortedSequence.t` + * + * @example + * + * ```ts + * import { SortedSequence, Num } from 'tiinvo' + * + * const s = SortedSequence.make(Num, 1, 2, 3) + * + * SortedSequence.length(s) // 3 + * ``` + * + * @since 4.0.0 + */ +export const length = Sequence.length; + +/** + * Gets values of a `SortedSequence.t` as an immutable indexed object. + * + * @example + * + * ```ts + * import { SortedSequence, Str } from 'tiinvo' + * + * const s = SortedSequence.make(Str, 'hello', 'world') + * + * SortedSequence.values(s) // { 0: 'hello', 1: 'world' } + * ``` + * + * @since 4.0.0 + */ +export const values = Sequence.values; + +//#endregion + +//#region predicates + +/** + * Returns `true` if the sorted list is empty. + * + * @example + * + * ```ts + * import { SortedSequence, Num } from 'tiinvo' + * + * const s = SortedSequence.make(Num) + * const s1 = SortedSequence.make(Num, 10) + * + * SortedSequence.empty(s) // true + * SortedSequence.empty(s1) // false + * ``` + * + * @since 4.0.0 + */ +export const empty = Sequence.empty; + +/** + * Returns `true` if the sorted list is populated. + * + * @example + * + * ```ts + * import { SortedSequence, Num } from 'tiinvo' + * + * const s = SortedSequence.make(Num, 10, 20, 30) + * + * SortedSequence.populated(s) // true + * SortedSequence.populated(SortedSequence.make(Num)) // false + * ``` + * + * @since 4.0.0 + */ +export const populated = Sequence.populated; + +//#endregion + +//#region serializables + +/** + * Converts a `SortedSequence.t` to an array of `a[]` + * + * @example + * + * ```ts + * import { SortedSequence, Num } from 'tiinvo' + * + * const sl = SortedSequence.make(Num, 3, 2, 1) + * + * SortedSequence.toArray(sl) // [1, 2, 3] + * ``` + * + * @since 4.0.0 + */ +export const toArray = Sequence.toArray; + +/** + * Converts a `SortedSequence.t` to a jsonizable value + * + * @example + * + * ```ts + * import { SortedSequence, Num } from 'tiinvo' + * + * const sl = SortedSequence.make(Num, 3, 2, 1) + * + * SortedSequence.toJSON(sl) // [1, 2, 3] + * ``` + * + * @since 4.0.0 + */ +export const toJSON = Sequence.toJSON; + +/** + * Converts a `SortedSequence.t` to a set of `Set` + * + * @example + * + * ```ts + * import { SortedSequence, Num } from 'tiinvo' + * + * const sl = SortedSequence.make(Num, 3, 2, 1) + * + * SortedSequence.toMap(sl) // Map([0, 1], [1, 2], [2, 3]) + * ``` + * + * @since 4.0.0 + */ +export const toMap = Sequence.toMap; + +/** + * Converts a `SortedSequence.t` to a set of `Set` + * + * @example + * + * ```ts + * import { SortedSequence, Num } from 'tiinvo' + * + * const sl = SortedSequence.make(Num, 3, 2, 1) + * + * SortedSequence.toSet(sl) // Set(1, 2, 3) + * ``` + * + * @since 4.0.0 + */ +export const toSet = Sequence.toSet; + +/** + * Converts a `SortedSequence.t` to a string + * + * @example + * + * ```ts + * import { SortedSequence, Num } from 'tiinvo' + * + * const sl = SortedSequence.make(Num, 3, 2, 1) + * + * SortedSequence.toString(sl) // "1,2,3" + * ``` + * + * @since 4.0.0 + */ +export const toString = Sequence.toString; + +//#endregion diff --git a/src/Str.test.ts b/src/Str.test.ts new file mode 100644 index 0000000..fea5cf4 --- /dev/null +++ b/src/Str.test.ts @@ -0,0 +1,156 @@ +import { describe, expect, test } from 'vitest'; +import * as Str from './Str.js'; + +describe("Str", () => { + test(Str.guard.name, () => { + expect(Str.guard('')).toEqual(true); + expect(Str.guard(1)).toEqual(false); + }); + test(Str.cmp.name, () => { + expect(Str.cmp('a', 'a')).toEqual(0); + expect(Str.cmp('a', 'b')).toEqual(-1); + expect(Str.cmp('b', 'a')).toEqual(1); + }); + test(Str.eq.name, () => { + expect(Str.eq('a', 'a')).toEqual(true); + expect(Str.eq('a', 'b')).toEqual(false); + expect(Str.eq('b', 'a')).toEqual(false); + }); + test(Str.asc.name, () => { + const collection = ['a', 'd', 'c', 'e', 'F', 'A']; + + expect(collection.sort(Str.asc)).toEqual(["A", "F", "a", "c", "d", "e"]); + expect(Str.asc("a")("b")).toEqual(1); + }); + test(Str.desc.name, () => { + const collection = ['a', 'd', 'c', 'e', 'F', 'A']; + + expect(collection.sort(Str.desc)).toEqual(["e", "d", "c", "a", "F", "A"]); + expect(Str.desc("a")("b")).toEqual(-1); + }); + test(Str.camel.name, () => { + expect(Str.camel('hello world')).toEqual('helloWorld'); + }); + test(Str.charAt.name, () => { + expect(Str.charAt("hello", 0)).toEqual("h"); + expect(Str.charAt("hello", 10)).toEqual(null); + expect(Str.charAt(0)("hello")).toEqual("h"); + expect(Str.charAt({} as any)("hello")).toEqual(null); + expect(Str.charAt(9)("hello")).toEqual(null); + }); + test(Str.charCodeAt.name, () => { + expect(Str.charCodeAt("hello", 0)).toEqual(104); + expect(Str.charCodeAt("hello", 10)).toEqual(null); + expect(Str.charCodeAt(0)("hello")).toEqual(104); + expect(Str.charCodeAt({} as any)("hello")).toEqual(null); + expect(Str.charCodeAt(9)("hello")).toEqual(null); + }); + test(Str.chars.name, () => { + expect(Str.chars('hello')).toEqual(['h', 'e', 'l', 'l', 'o']); + }); + test(Str.concat.name, () => { + expect(Str.concat("hello", "world")).toEqual("helloworld"); + expect(Str.concat("world")("hello")).toEqual("helloworld"); + }); + test(Str.endsWith.name, () => { + expect(Str.endsWith("hello", "o")).toEqual(true); + expect(Str.endsWith("o")("hello")).toEqual(true); + }); + test(Str.includes.name, () => { + expect(Str.includes("hello", "o")).toEqual(true); + expect(Str.includes("o")("hello")).toEqual(true); + }); + test(Str.indexOf.name, () => { + expect(Str.indexOf("hello", "l")).toEqual(2); + expect(Str.indexOf("hello", "l", 3)).toEqual(3); + expect(Str.indexOf("l")("hello")).toEqual(2); + expect(Str.indexOf("l")("hello", 3)).toEqual(3); + }); + test(Str.lastIndexOf.name, () => { + expect(Str.lastIndexOf("hello", "l")).toEqual(3); + expect(Str.lastIndexOf("l")("hello")).toEqual(3); + }); + test(Str.length.name, () => { + expect(Str.length('hello')).toEqual(5); + }); + test(Str.lines.name, () => { + expect(Str.lines('hello\nworld')).toEqual(['hello', 'world']); + }); + test(Str.lower.name, () => { + expect(Str.lower('HELLO')).toEqual("hello"); + }); + test(Str.match.name, () => { + expect(Str.match("hello", "o")?.[0]).oneOf(['o']); + expect(Str.match("o")("hello")?.[0]).oneOf(['o']); + }); + test(Str.padEnd.name, () => { + expect(Str.padEnd("a", 5)).toEqual("a "); + expect(Str.padEnd("a", 5, "b")).toEqual("abbbb"); + expect(Str.padEnd(5)("a")).toEqual("a "); + expect(Str.padEnd(5, "b")("a")).toEqual("abbbb"); + expect(Str.padEnd(5)("a", "b")).toEqual("abbbb"); + }); + test(Str.pascal.name, () => { + expect(Str.pascal('hello world')).toEqual('HelloWorld'); + }); + test(Str.padStart.name, () => { + expect(Str.padStart("a", 5)).toEqual(" a"); + expect(Str.padStart("a", 5, "b")).toEqual("bbbba"); + expect(Str.padStart(5)("a")).toEqual(" a"); + expect(Str.padStart(5, "b")("a")).toEqual("bbbba"); + expect(Str.padStart(5)("a", "b")).toEqual("bbbba"); + }); + test(Str.repeat.name, () => { + expect(Str.repeat("a", 5)).toEqual("aaaaa"); + expect(Str.repeat(5)("a")).toEqual("aaaaa"); + }); + test(Str.replace.name, () => { + expect(Str.replace("hello", "l", "e")).toEqual("heelo"); + expect(Str.replace("l", "e")("hello")).toEqual("heelo"); + }); + test(Str.reverse.name, () => { + expect(Str.reverse('hello')).toEqual('olleh'); + }); + test(Str.search.name, () => { + expect(Str.search("hello", "l")).toEqual(2); + expect(Str.search("l")("hello")).toEqual(2); + expect(Str.search("x")("hello")).toEqual(null); + expect(Str.search("hello", "x")).toEqual(null); + }); + test(Str.slice.name, () => { + expect(Str.slice("hello", 1)).toEqual("ello"); + expect(Str.slice("hello", 1, 3)).toEqual("el"); + expect(Str.slice(1)("hello")).toEqual("ello"); + expect(Str.slice(1, 3)("hello")).toEqual("el"); + }); + test(Str.split.name, () => { + const x = (arg: string) => { + return { + [Symbol.split](str: string) { + return str.split(arg); + } + }; + }; + + expect(Str.split("hello world", " ")).toEqual(["hello", "world"]); + expect(Str.split("hello world", /\s/)).toEqual(["hello", "world"]); + expect(Str.split("hello world", x(" "))).toEqual(["hello", "world"]); + expect(Str.split(" ")("hello world")).toEqual(["hello", "world"]); + expect(Str.split(" ", 1)("hello world")).toEqual(["hello"]); + }); + test(Str.trim.name, () => { + expect(Str.trim(' hello world ')).toEqual('hello world'); + }); + test(Str.trimEnd.name, () => { + expect(Str.trimEnd(' hello world ')).toEqual(' hello world'); + }); + test(Str.trimStart.name, () => { + expect(Str.trimStart(' hello world ')).toEqual('hello world '); + }); + test(Str.upper.name, () => { + expect(Str.upper('hello')).toEqual("HELLO"); + }); + test(Str.words.name, () => { + expect(Str.words('hello world')).toEqual(['hello', 'world']); + }); +}); \ No newline at end of file diff --git a/src/TypedSequence.test.ts b/src/TypedSequence.test.ts new file mode 100644 index 0000000..a1edad3 --- /dev/null +++ b/src/TypedSequence.test.ts @@ -0,0 +1,150 @@ +import { describe, expect, test } from 'vitest'; +import * as Num from './Num.js'; +import * as Str from './Str.js'; +import * as Sequence from './Sequence.js'; +import * as TypedSequence from './TypedSequence.js'; + +describe("TypedSequence", () => { + test(TypedSequence.make.name, () => { + const ts0 = TypedSequence.make(Num, 1, 2, 3); + const ts1 = TypedSequence.make(Num.guard, 1, 2, 3); + + expect(() => TypedSequence.make(null as any)).toThrow(); + expect(() => TypedSequence.make({} as any, 1, 2, 3)).toThrow(); + expect(() => TypedSequence.make(Num, 1, 2, "hello" as any)).toThrow(); + expect(Sequence.eq(Num, ts0, ts1)).toEqual(true); + }); + + test(TypedSequence.append.name, () => { + const s0 = TypedSequence.make(Num, 10, 20); + + expect(TypedSequence.append(s0, 30)).toEqual(TypedSequence.make(Num, 10, 20, 30)); + expect(TypedSequence.append(30)(s0)).toEqual(TypedSequence.make(Num, 10, 20, 30)); + }); + + test(TypedSequence.prepend.name, () => { + const s0 = TypedSequence.make(Num, 10, 20); + + expect(TypedSequence.prepend(s0, 30)).toEqual(TypedSequence.make(Num, 30, 10, 20)); + expect(TypedSequence.prepend(30)(s0)).toEqual(TypedSequence.make(Num, 30, 10, 20)); + }); + + test(TypedSequence.concat.name, () => { + const s0 = TypedSequence.make(Num, 10, 20); + const s1 = TypedSequence.make(Num, 30, 40); + + expect(TypedSequence.concat(s0, s1)).toEqual(TypedSequence.make(Num, 10, 20, 30, 40)); + expect(TypedSequence.concat(s1)(s0)).toEqual(TypedSequence.make(Num, 10, 20, 30, 40)); + }); + + test(TypedSequence.guard.name, () => { + const a = TypedSequence.make(Num); + const b = Sequence.make(); + + expect(TypedSequence.guard(a)).toEqual(true); + expect(TypedSequence.guard(b)).toEqual(false); + }); + + test(TypedSequence.sort.name, () => { + const a = TypedSequence.make(Num, 5, 3, 1, 4, 2); + const b = TypedSequence.sort(a, Num); + const c = TypedSequence.sort(a, Num.cmp); + const d = TypedSequence.sort(Num.cmp)(a); + + expect(b).toEqual(TypedSequence.make(Num, 1, 2, 3, 4, 5)); + expect(b).toEqual(c); + expect(d).toEqual(c); + }); + + test(TypedSequence.count.name, () => { + const s = TypedSequence.make(Num, 10, 20, 30); + + expect(TypedSequence.count(s, Num.gt(10))).toEqual(2); + expect(TypedSequence.count(Num.gt(10))(s)).toEqual(2); + }); + + test(TypedSequence.get.name, () => { + const s = TypedSequence.make(Str, 'hello', 'world'); + + expect(TypedSequence.get(s, 0)).toEqual('hello'); + expect(TypedSequence.get(s, 9)).toEqual(new RangeError("Index out of bounds 9 for length 2")); + }); + + test(TypedSequence.first.name, () => { + const s0 = TypedSequence.make(Num, 10, 20, 30); + const s1 = TypedSequence.make(Num); + + expect(TypedSequence.first(s0)).toEqual(10); + expect(TypedSequence.first(s1)).toEqual(null); + }); + + test(TypedSequence.last.name, () => { + const s0 = TypedSequence.make(Num, 10, 20, 30); + const s1 = TypedSequence.make(Num); + + expect(TypedSequence.last(s0)).toEqual(30); + expect(TypedSequence.last(s1)).toEqual(null); + }); + + test(TypedSequence.length.name, () => { + const s = TypedSequence.make(Num, 1, 2, 3); + + expect(TypedSequence.length(s)).toEqual(3); + }); + + test(TypedSequence.values.name, () => { + const s = TypedSequence.make(Str, 'hello', 'world'); + + expect(TypedSequence.values(s)).toEqual({ 0: 'hello', 1: 'world' }); + }); + + test(TypedSequence.empty.name, () => { + const s = TypedSequence.make(Num); + const s1 = TypedSequence.make(Num, 10); + + expect(TypedSequence.empty(s)).toEqual(true); + expect(TypedSequence.empty(s1)).toEqual(false); + }); + + test(TypedSequence.populated.name, () => { + const s = TypedSequence.make(Num, 10, 20, 30); + + expect(TypedSequence.populated(s)).toEqual(true); + expect(TypedSequence.populated(TypedSequence.make(Num))).toEqual(false); + }); + + test(TypedSequence.toArray.name, () => { + const sl = TypedSequence.make(Num, 3, 2, 1); + + expect(TypedSequence.toArray(sl)).toEqual([3, 2, 1]); + }); + + test(TypedSequence.toJSON.name, () => { + const sl = TypedSequence.make(Num, 3, 2, 1); + + expect(TypedSequence.toJSON(sl)).toEqual([3, 2, 1]); + }); + + test(TypedSequence.toMap.name, () => { + const sl = TypedSequence.make(Num, 3, 2, 1); + const m = new Map(); + + m.set("0", 3); + m.set("1", 2); + m.set("2", 1); + + expect(TypedSequence.toMap(sl)).toEqual(m); + }); + + test(TypedSequence.toSet.name, () => { + const sl = TypedSequence.make(Num, 3, 2, 1); + + expect(TypedSequence.toSet(sl)).toEqual(new Set([3, 2, 1])); + }); + + test(TypedSequence.toString.name, () => { + const sl = TypedSequence.make(Num, 3, 2, 1); + + expect(TypedSequence.toString(sl)).toEqual("3,2,1"); + }); +}); diff --git a/src/TypedSequence.ts b/src/TypedSequence.ts new file mode 100644 index 0000000..9b9e75c --- /dev/null +++ b/src/TypedSequence.ts @@ -0,0 +1,455 @@ +import type * as Fn from './Fn.js'; +import type * as Functors from './Functors.js'; +import * as Sequence from './Sequence.js'; + +const guardsymbol = Symbol('sequence.guard'); + +/** + * The typed version of a `Sequence.t`. + */ +export type t = Sequence.t & { + [guardsymbol]: Functors.Guardable; +}; + +//#region factories + +/** + * Creates a `TypedSequence.t` starting from a `Guardable` or a `GuardableModule` + * + * You can add initial values as arguments. + * + * If an argument does pass the guard check, the function will throw a TypeError + * + * @example + * + * ```ts + * import { TypedSequence, Num } from 'tiinvo' + * + * const ts0 = TypedSequence.make(Num, 1, 2, 3) + * const ts1 = TypedSequence.make(Num.guard, 1, 2, 3) + * + * ts0 === ts1 // true + * ``` + * + * @since 4.0.0 + */ +export function make(g: Functors.Guardable, ...values: a[]): t; +export function make(g: Functors.GuardableModule, ...values: a[]): t; +export function make(g: Functors.Guardable | Functors.GuardableModule, ...values: a[]): t { + if (typeof g === 'function' || ('guard' in g && typeof g.guard === 'function')) { + const _t = Sequence.make.apply(null, values) as t; + + if (typeof g === 'function') { + if (values.every(g)) { + _t[guardsymbol] = g; + + return _t; + } + } else { + if (values.every(g.guard)) { + _t[guardsymbol] = g.guard; + + return _t; + } + } + + throw new TypeError("Non homogeneous arguments"); + } else { + throw new TypeError("Invalid guard argument, must be a Guardable or GuardableModule functor"); + } +} + +//#endregion + +//#region guards + +/** + * Checks if the parameter `x` is a `TypedSequence.t` + * + * @example + * + * ```ts + * import { TypedSequence, Sequence, Num } from 'tiinvo' + * + * const a = TypedSequence.make(Num) + * const b = Sequence.make() + * + * TypedSequence.guard(a) // true + * TypedSequence.guard(b) // false + * ``` + * + * @since 4.0.0 + */ +export const guard = (x: unknown): x is t => Sequence.guard(x) && guardsymbol in x; + +//#endregion + +//#region operators + +/** + * Adds an element to the end of the `Sequence.t` without mutating the original one. + * + * @example + * + * ```ts + * import { TypedSequence, Num } from 'tiinvo' + * + * const s0 = TypedSequence.make(Num, 10, 20) + * + * TypedSequence.append(s0, 30) // TypedSequence(10, 20, 30) + * TypedSequence.append(30)(s0) // TypedSequence(10, 20, 30) + * ``` + * + * @since 4.0.0 + */ +export function append(a: t, b: a): t; +export function append(a: a): Fn.Unary, t>; +export function append(a: t | a, b?: any): any { + if (guard(a) && arguments.length === 2) { + const p = [{ guard: a[guardsymbol] }, ...a, b] as Parameters; + return make.apply(null, p); + } + + return (b: t) => { + const p = [{ guard: b[guardsymbol] }, ...b, a] as Parameters; + return make.apply(null, p); + }; +} + +/** + * Concatenates two `TypedSequence.t` and `TypedSequence.t` and return a new `TypedSequence.t`. + * + * @example + * + * ```ts + * import { TypedSequence, Num } from 'tiinvo' + * + * const s0 = TypedSequence.make(Num, 10, 20) + * const s1 = TypedSequence.make(Num, 30, 40) + * + * TypedSequence.concat(s0, s1) // TypedSequence(10, 20, 30, 40) + * TypedSequence.concat(s1)(s0) // TypedSequence(10, 20, 30, 40) + * ``` + * + * @since 4.0.0 + */ +export function concat>(a: a, b: a): a; +export function concat>(a: a): Fn.Unary; +export function concat>(a: a, b?: a): any { + if (guard(a) && guard(b)) { + return make.apply(null, [{ guard: a[guardsymbol] }, ...a, ...b]); + } + + return (b: a) => make.apply(null, [{ guard: b[guardsymbol] }, ...b, ...a]); +} + +/** + * Adds an element to the start of the `TypedSequence.t` without mutating the original one. + * + * @example + * + * ```ts + * import { TypedSequence, Num } from 'tiinvo' + * + * const s0 = TypedSequence.make(Num, 10, 20) + * + * TypedSequence.prepend(s0, 30) // TypedSequence.make(Num, 30, 10, 20) + * TypedSequence.prepend(30)(s0) // TypedSequence.make(Num, 30, 10, 20) + * ``` + * + * @since 4.0.0 + */ +export function prepend(a: t, b: a): t; +export function prepend(a: a): Fn.Unary, t>; +export function prepend(a: any, b?: any): any { + if (guard(a)) { + const x = [{ guard: a[guardsymbol] }, b, ...a] as Parameters; + + return make.apply(null, x); + } + + return (b: t) => { + const p = [{ guard: b[guardsymbol] }, a, ...b] as Parameters; + return make.apply(null, p); + }; +} + +/** + * Sorts and returns a new `TypedSequence.t` values with a `Comparable` or `ComparableModule` functor. + * + * @example + * + * ```ts + * import { TypedSequence, Num } from 'tiinvo' + * + * const a = TypedSequence.make(Num, 5, 3, 1, 4, 2) + * const b = TypedSequence.sort(a, Num) + * + * b // TypedSequence.make(Num, 1, 2, 3, 4, 5) + * ``` + * + * @since 4.0.0 + */ +export function sort(a: t, mod: Functors.Comparable | Functors.ComparableModule): t; +export function sort(a: Functors.Comparable | Functors.ComparableModule): Fn.Unary, t>; +export function sort(a: t | Functors.Comparable | Functors.ComparableModule, mod?: Functors.Comparable | Functors.ComparableModule): any { + const getcmp = (x: Functors.Comparable | Functors.ComparableModule) => typeof x === 'function' ? x : x.cmp; + + if (arguments.length === 2 && guard(a)) { + const p = [{ guard: a[guardsymbol] }, ... Array.from(a).sort(getcmp(mod!))] as Parameters; + return make.apply(null, p); + } + + return (x: t) => { + const p = [{ guard: x[guardsymbol] }, ... Array.from(x).sort(getcmp(a as Functors.Comparable | Functors.ComparableModule))] as Parameters; + return make.apply(null, p) + }; +} + +//#endregion + +//#region getters + +/** + * Counts the number of elements that satisfy a given predicate + * + * @example + * + * ```ts + * import { TypedSequence, Num } from 'tiinvo' + * + * const s = TypedSequence.make(Num, 10, 20, 30) + * + * TypedSequence.count(s, Num.gt(10)) // 2 + * TypedSequence.count(Num.gt(10))(s) // 2 + * ``` + * + * @since 4.0.0 + */ + export const count = Sequence.count; + + /** + * + * + * @example + * + * ```ts + * import { TypedSequence, Str } from 'tiinvo' + * + * const s = TypedSequence.make(Str, 'hello', 'world') + * + * TypedSequence.get(s, 0) // 'hello' + * TypedSequence.get(s, 9) // throws RangeError + * ``` + * + * @since 4.0.0 + */ + export const get = Sequence.get; + + /** + * + * + * @example + * + * ```ts + * import { TypedSequence, Num } from 'tiinvo' + * + * const s0 = TypedSequence.make(Num, 10, 20, 30) + * const s1 = TypedSequence.make(Num) + * + * TypedSequence.first(s0) // 10 + * TypedSequence.first(s1) // null + * ``` + * + * @since 4.0.0 + */ + export const first = Sequence.first; + + /** + * Gets a Sequence.t's last element. + * + * Returns Option.None if none is found. + * + * @example + * + * ```ts + * import { TypedSequence, Num } from 'tiinvo' + * + * const s0 = TypedSequence.make(Num, 10, 20, 30) + * const s1 = TypedSequence.make(Num) + * + * TypedSequence.last(s0) // 30 + * TypedSequence.last(s1) // null + * ``` + * ``` + * + * @since 4.0.0 + */ + export const last = Sequence.last; + + /** + * Gets the length of a `TypedSequence.t` + * + * @example + * + * ```ts + * import { TypedSequence, Num } from 'tiinvo' + * + * const s = TypedSequence.make(Num, 1, 2, 3) + * + * TypedSequence.length(s) // 3 + * ``` + * + * @since 4.0.0 + */ + export const length = Sequence.length; + + /** + * Gets values of a `TypedSequence.t` as an immutable indexed object. + * + * @example + * + * ```ts + * import { TypedSequence, Str } from 'tiinvo' + * + * const s = TypedSequence.make(Str, 'hello', 'world') + * + * TypedSequence.values(s) // { 0: 'hello', 1: 'world' } + * ``` + * + * @since 4.0.0 + */ + export const values = Sequence.values; + + //#endregion + + //#region predicates + + /** + * Returns `true` if the sorted list is empty. + * + * @example + * + * ```ts + * import { TypedSequence, Num } from 'tiinvo' + * + * const s = TypedSequence.make(Num) + * const s1 = TypedSequence.make(Num, 10) + * + * TypedSequence.empty(s) // true + * TypedSequence.empty(s1) // false + * ``` + * + * @since 4.0.0 + */ + export const empty = Sequence.empty; + + /** + * Returns `true` if the sorted list is populated. + * + * @example + * + * ```ts + * import { TypedSequence, Num } from 'tiinvo' + * + * const s = TypedSequence.make(Num, 10, 20, 30) + * + * TypedSequence.populated(s) // true + * TypedSequence.populated(TypedSequence.make(Num)) // false + * ``` + * + * @since 4.0.0 + */ + export const populated = Sequence.populated; + + //#endregion + + //#region serializables + + /** + * Converts a `TypedSequence.t` to an array of `a[]` + * + * @example + * + * ```ts + * import { TypedSequence, Num } from 'tiinvo' + * + * const sl = TypedSequence.make(Num, 3, 2, 1) + * + * TypedSequence.toArray(sl) // [3, 2, 1] + * ``` + * + * @since 4.0.0 + */ + export const toArray = Sequence.toArray; + + /** + * Converts a `TypedSequence.t` to a jsonizable value + * + * @example + * + * ```ts + * import { TypedSequence, Num } from 'tiinvo' + * + * const sl = TypedSequence.make(Num, 3, 2, 1) + * + * TypedSequence.toJSON(sl) // [3, 2, 1] + * ``` + * + * @since 4.0.0 + */ + export const toJSON = Sequence.toJSON; + + /** + * Converts a `TypedSequence.t` to a set of `Set` + * + * @example + * + * ```ts + * import { TypedSequence, Num } from 'tiinvo' + * + * const sl = TypedSequence.make(Num, 3, 2, 1) + * + * TypedSequence.toMap(sl) // Map([0, 3], [1, 2], [2, 1]) + * ``` + * + * @since 4.0.0 + */ + export const toMap = Sequence.toMap; + + /** + * Converts a `TypedSequence.t` to a set of `Set` + * + * @example + * + * ```ts + * import { TypedSequence, Num } from 'tiinvo' + * + * const sl = TypedSequence.make(Num, 3, 2, 1) + * + * TypedSequence.toSet(sl) // Set(3, 2, 1) + * ``` + * + * @since 4.0.0 + */ + export const toSet = Sequence.toSet; + + /** + * Converts a `TypedSequence.t` to a string + * + * @example + * + * ```ts + * import { TypedSequence, Num } from 'tiinvo' + * + * const sl = TypedSequence.make(Num, 3, 2, 1) + * + * TypedSequence.toString(sl) // "3,2,1" + * ``` + * + * @since 4.0.0 + */ + export const toString = Sequence.toString; + + //#endregion + \ No newline at end of file diff --git a/src/__tests__/array.test.ts b/src/__tests__/array.test.ts deleted file mode 100644 index 7b09aaf..0000000 --- a/src/__tests__/array.test.ts +++ /dev/null @@ -1,276 +0,0 @@ -import { describe, expect, test } from 'vitest'; -import * as a from '../array'; -import * as n from '../num'; -import * as o from '../option'; -import * as s from '../str'; - -describe(`array`, () => { - test(a.cmp.name, () => { - const a1 = [1, 2, 3]; - const a2 = [1, 2, 3]; - const a3 = [1, 2, 4]; - - expect(a.cmp(null as any, null as any)).toBe(0); - expect(a.cmp(undefined as any, undefined as any)).toBe(0); - expect(a.cmp(a1, a2)).toBe(0); - expect(a.cmp(a1, a3)).toBe(-1); - expect(a.cmp(a3, a1)).toBe(1); - }); - - test(a.concat.name, () => { - const a1 = [1, 2, 3]; - const a2 = [4, 5, 6]; - - expect(a.concat(a2)(a1)).toEqual([1, 2, 3, 4, 5, 6]); - }); - - test(a.contains.name, () => { - const a1 = [1, 2, 3]; - - expect(a.contains(2)(a1)).toBe(true); - expect(a.contains(4)(a1)).toBe(false); - }); - - test(a.empty.name, () => { - expect(a.empty([])).toBe(true) - expect(a.empty([1, 2, 3])).toBe(false) - }); - - test(a.eq.name, () => { - const a1 = [1, 2, 3]; - const a2 = [1, 2, 3]; - const a3 = [1, 2, 4]; - - expect(a.eq(a1, a2)).toBe(true); - expect(a.eq(a1, a3)).toBe(false); - }); - - test(a.every.name, () => { - const a1 = [1, 2, 3]; - - expect(a.every(n.gt(0))(a1)).toBe(true); - expect(a.every(n.gt(1))(a1)).toBe(false); - }); - - test(a.filter.name, () => { - const a1 = [1, 2, 3]; - - expect(a.filter(n.gt(0))(a1)).toEqual([1, 2, 3]); - expect(a.filter(n.gt(1))(a1)).toEqual([2, 3]); - }); - - test(a.find.name, () => { - const a1 = [1, 2, 3]; - - expect(a.find(n.gt(0))(a1)).toBe(1); - expect(a.find(n.gt(1))(a1)).toBe(2); - expect(a.find(n.gt(2))(a1)).toBe(3); - }); - - test(a.first.name, () => { - const a1 = [1, 2, 3]; - - expect(a.first(a1)).toBe(1); - }); - - test(a.firstOr.name, () => { - const a1 = [1, 2, 3]; - - expect(a.firstOr(4)(a1)).toBe(1); - expect(a.firstOr(4)([])).toBe(4); - }); - - test(a.flat.name, () => { - const a1 = [[1, 2, 3], [4, 5, 6]]; - - expect(expect.arrayContaining(a.flat(a1))).toEqual([1, 2, 3, 4, 5, 6]); - }); - - test(a.fromfunctions.name, () => { - const fn1 = (x: number) => x + 1; - const fn2 = (x: string) => x.length; - - expect(a.fromfunctions(fn1, fn2)([10, "hello"])).toEqual([11, 5]); - }); - - test(a.get.name, () => { - const a1 = [1, 2, 3]; - - expect(a.get(1)(a1)).toBe(2); - expect(a.get(3)(a1)).toEqual(new Error(`Index out of bounds 3 for length 3`)); - }); - - test(a.guard.name, () => { - expect(a.guard([])).toBe(true); - expect(a.guard([1, 2, 3])).toBe(true); - expect(a.guard(`hello world`)).toBe(false); - }); - - test(a.guardOf.name, () => { - const numguard = a.guardOf(n.guard); - - expect(numguard([])).toBe(true); - expect(numguard([1, 2, 3])).toBe(true); - expect(numguard([`hello world`])).toBe(false); - }); - - test(a.join.name, () => { - const a1 = [1, 2, 3]; - - expect(a.join(`-`)(a1)).toBe(`1-2-3`); - expect(a.join()(a1)).toBe(`123`); - }); - - test(a.last.name, () => { - const a1 = [1, 2, 3]; - - expect(a.last(a1)).toBe(3); - expect(o.isNone(a.last([]))).toBeTruthy(); - }); - - test(a.lastOr.name, () => { - const a1 = [1, 2, 3]; - - expect(a.lastOr(4)(a1)).toBe(3); - expect(a.lastOr(4)([])).toBe(4); - }); - - test(a.map.name, () => { - const inc = n.uadd(1); - - expect(expect.arrayContaining(a.map(inc)([1, 2, 3]))).toEqual([2, 3, 4]); - }); - - test(a.mapfilter.name, () => { - const fm = a.mapfilter(n.umul(2), n.gt(3)); - - expect(expect.arrayContaining(fm([1, 2, 3]))).toEqual([4, 6]); - }) - - test(a.none.name, () => { - const p = n.isEven; - - expect(a.none(p)([1, 2, 3])).toBe(false); - expect(a.none(p)([2, 4, 6])).toBe(false); - expect(a.none(p)([3, 7, 9])).toBe(true); - }); - - test(a.rand.name, () => { - const a1 = [1, 2, 3]; - - expect(a1.includes(a.rand(a1))).toBeTruthy() - expect(a1.includes(a.rand(a1))).toBeTruthy() - expect(a1.includes(a.rand(a1))).toBeTruthy() - expect(a1.includes(a.rand(a1))).toBeTruthy() - expect(a1.includes(a.rand(a1))).toBeTruthy() - expect(a1.includes(a.rand(a1))).toBeTruthy() - expect(a1.includes(a.rand(a1))).toBeTruthy() - expect(a1.includes(a.rand(a1))).toBeTruthy() - expect(a1.includes(a.rand(a1))).toBeTruthy() - expect(a1.includes(a.rand(a1))).toBeTruthy() - }); - - test(a.reduce.name, () => { - const a1 = [1, 2, 3]; - const r = a.reduce(n.badd); - - expect(r(0)(a1)).toBe(6); - }); - - test(a.reduceright.name, () => { - const a1 = [1, 2, 3]; - const r = a.reduceright(n.bsub); - - expect(r(0)(a1)).toBe(-6); - }); - - test(a.reverse.name, () => { - const a1 = [1, 2, 3]; - - expect(expect.arrayContaining(a.reverse(a1))).toEqual([3, 2, 1]); - }); - - test(a.shuffle.name, () => { - const a1 = [1, 2, 3]; - - expect(a.shuffle(a1).includes(1)).toBeTruthy(); - expect(a.shuffle(a1).includes(2)).toBeTruthy(); - expect(a.shuffle(a1).includes(3)).toBeTruthy(); - }); - - test(a.slice.name, () => { - const a1 = [1, 2, 3]; - - expect(expect.arrayContaining(a.slice(1, 3)(a1))).toEqual([2, 3]); - expect(expect.arrayContaining(a.slice(undefined, 1)(a1))).toEqual([1]); - expect(expect.arrayContaining(a.slice(2)(a1))).toEqual([3]); - }); - - test(a.some.name, () => { - const p = n.isEven; - - expect(a.some(p)([1, 2, 3])).toBe(true); - expect(a.some(p)([2, 4, 6])).toBe(true); - expect(a.some(p)([3, 7, 9])).toBe(false); - }); - - test(a.sort.name, () => { - const a1 = [1, 2, 3]; - const a2 = [3, 2, 1]; - - expect(expect.arrayContaining(a.sort(n.asc)(a2))).toEqual([1, 2, 3]); - expect(expect.arrayContaining(a.sort(n.desc)(a1))).toEqual([3, 2, 1]); - }); - - test(a.from.name, () => { - const a1 = [1, 2, 3]; - const s = new Set(a1); - - expect(expect.arrayContaining(a.from(a1))).toEqual([1, 2, 3]); - expect(expect.arrayContaining(a.from(s))).toEqual([1, 2, 3]); - }) - - test(a.length.name, () => { - const a1 = [1, 2, 3]; - - expect(a.length(a1)).toBe(3); - }) - - test(a.of.name, () => { - const a1 = [1, 2, 3]; - - expect(expect.arrayContaining(a.of(1, 2, 3))).toEqual([1, 2, 3]); - }) - - test(a.flatmap.name, () => { - const map = a.flatmap(s.length); - - expect(expect.arrayContaining(map([['abc'], ['cdef']]))).toEqual([3, 4]); - }) - - test(a.includes.name, () => { - expect(a.includes('a')(['a', 'b'])).toBe(true); - expect(a.includes('c')(['a', 'b'])).toBe(false); - }) - - test(a.filtermap.name, () => { - const fm1 = a.filtermap(n.umul(2), n.gt(3)); - - expect(expect.arrayContaining(fm1([1, 2, 3, 4, 5]))).toEqual([6, 8, 10]); - }) - - test(a.fill.name, () => { - const f1 = a.fill(0, 1, 2); - const f2 = a.fill(0, 1); - const f3 = a.fill(0); - expect(expect.arrayContaining(f1([10, 20, 30, 40]))).toEqual([10, 0, 0, 40]) - expect(expect.arrayContaining(f2([10, 20, 30, 40]))).toEqual([10, 0, 0, 0]) - expect(expect.arrayContaining(f3([10, 20, 30, 40]))).toEqual([0, 0, 0, 0]) - }) - - test(a.make.name, () => { - expect(expect.arrayContaining(a.make(4, 'hello'))).toEqual(['hello', 'hello', 'hello', 'hello']) - expect(expect.arrayContaining(a.make(2))).toEqual([undefined, undefined]) - expect(expect.arrayContaining(a.make())).toEqual([]) - }) -}) \ No newline at end of file diff --git a/src/__tests__/assertable.test.ts b/src/__tests__/assertable.test.ts deleted file mode 100644 index 8d1d9ed..0000000 --- a/src/__tests__/assertable.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { describe, expect, test } from 'vitest'; -import * as Assert from '../assertable'; -import * as Number from '../num'; -import * as Result from '../result'; - -describe(`assertable`, () => { - test(Assert.check.name, () => { - expect(() => Assert.check(true, 'This will not throw an error')).not.toThrow(); - expect(() => Assert.check(false, 'This will throw an error')).toThrow(); - }) - - test(Assert.checkr.name, () => { - expect(Result.isOk(Assert.checkr(true, 'This will not throw an error'))).toBeTruthy(); - expect(Result.isErr(Assert.checkr(false, 'This will throw an error'))).toBeTruthy(); - }) - - test(Assert.make.name, () => { - const check1 = Assert.make(Number.isEven, 'number is not even'); - const check2 = Assert.make(Number.isEven, n => n + ' is not even'); - - expect(() => check1(2)).not.toThrow(); - expect(() => check1(3)).toThrow(); - expect(() => check2(3)).toThrow(); - }) - - test(Assert.maker.name, () => { - const check1 = Assert.maker(Number.isEven, 'number is not even'); - const check2 = Assert.maker(Number.isEven, n => n + ' is not even'); - - expect(Result.isOk(check1(2))).toBeTruthy(); - expect(Result.isErr(check1(3))).toBeTruthy(); - expect(Result.isErr(check2(3))).toBeTruthy(); - }) -}); diff --git a/src/__tests__/bool.test.ts b/src/__tests__/bool.test.ts deleted file mode 100644 index 451de84..0000000 --- a/src/__tests__/bool.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { describe, expect, test } from 'vitest'; -import * as b from '../bool'; - -describe(`bool`, () => { - test(`cmp`, () => { - expect(b.cmp(true, true)).toBe(0) - expect(b.cmp(true, false)).toBe(1) - expect(b.cmp(false, true)).toBe(-1) - }) - - test(`eq`, () => { - expect(b.eq(true, true)).toBe(true) - expect(b.eq(true, false)).toBe(false) - expect(b.eq(false, true)).toBe(false) - }) - - test(`flip`, () => { - expect(b.flip(true)).toBe(false) - expect(b.flip(false)).toBe(true) - }) - - test(`guard`, () => { - expect(b.guard(true)).toBe(true) - expect(b.guard(false)).toBe(true) - expect(b.guard(0)).toBe(false) - }) - - test(`tobit`, () => { - expect(b.toBit(true)).toBe(1) - expect(b.toBit(false)).toBe(0) - }) -}) \ No newline at end of file diff --git a/src/__tests__/date.test.ts b/src/__tests__/date.test.ts deleted file mode 100644 index 0e612a7..0000000 --- a/src/__tests__/date.test.ts +++ /dev/null @@ -1,182 +0,0 @@ -import { describe, expect, test } from 'vitest'; -import * as d from '../date'; - -describe(`date`, () => { - test(d.cmp.name, () => { - const d1 = new Date(2018, 0, 1); - const d2 = new Date(2018, 0, 2); - - expect(d.cmp(d1, d2)).toBe(-1); - expect(d.cmp(d2, d1)).toBe(1); - expect(d.cmp(d1, d1)).toBe(0); - }); - test(d.guard.name, () => { - expect(d.guard(new Date())).toBe(true); - expect(d.guard(new Date(2018, 0, 1))).toBe(true); - expect(d.guard(new Date(2018, 0, 1, 12, 0, 0))).toBe(true); - expect(d.guard({})).toBe(false); - }); - test(d.eq.name, () => { - const d1 = d.make(2018, d.Month.Jan, 1); - const d2 = d.make(2018, d.Month.Jan, 1); - - expect(d.eq(d1, d2)); - }); - test(d.make.name, () => { - expect(d.make(2018, d.Month.Jan, 1)).toEqual(new Date(2018, 0, 1)); - }); - test(d.inrange.name, () => { - const d1 = new Date(2018, 0, 1); - const d2 = new Date(2018, 0, 2); - const d3 = new Date(2018, 0, 3); - - expect(d.inrange(d1, d3)(d2)).toBe(true); - expect(d.inrange(d1, d3)(d1)).toBe(true); - expect(d.inrange(d1, d3)(d3)).toBe(true); - expect(d.inrange(d1, d2)(d3)).toBe(false); - }); - test(d.invalid.name, () => { - expect(d.invalid(new Date())).toBe(false); - expect(d.invalid(new Date(2018, 0, 1))).toBe(false); - expect(d.invalid(new Date(2018, 0, 1, 12, 0, 0))).toBe(false); - expect(d.invalid(new Date(`a`))).toBe(true); - }); - test(d.isleap.name, () => { - expect(d.isleap(new Date(2018, 0, 1))).toBe(false); - expect(d.isleap(new Date(2016, 0, 1))).toBe(true); - expect(d.isleap(new Date(400, 0, 1))).toBe(true); - expect(d.isleap(new Date(100, 0, 1))).toBe(false); - }); - test(d.notinrange.name, () => { - const d1 = new Date(2018, 0, 1); - const d2 = new Date(2018, 0, 2); - const d3 = new Date(2018, 0, 3); - - expect(d.notinrange(d1, d3)(d2)).toBe(false); - expect(d.notinrange(d1, d3)(d1)).toBe(false); - expect(d.notinrange(d1, d3)(d3)).toBe(false); - expect(d.notinrange(d1, d2)(d3)).toBe(true); - }); - test(d.valid.name, () => { - expect(d.valid(new Date())).toBe(true); - expect(d.valid(new Date(2018, 0, 1))).toBe(true); - expect(d.valid(new Date(2018, 0, 1, 12, 0, 0))).toBe(true); - expect(d.valid(new Date(`a`))).toBe(false); - }); - test(d.add.name, () => { - const d1 = d.make(2018, d.Month.January, 1); - - expect(d.add()(d1)).toEqual(d1); - expect(d.add(1)(d1)).toEqual(d.make(2019, d.Month.Jan, 1)); - expect(d.add(1, 1)(d1)).toEqual(d.make(2019, d.Month.Feb, 1)); - expect(d.add(1, 1, 1)(d1)).toEqual(d.make(2019, d.Month.Feb, 2)); - }); - test(d.addYears.name, () => { - const d1 = d.make(2018, d.Month.January, 1); - const d2 = d.make(2019, d.Month.January, 1); - expect(d.addYears(1)(d1)).toEqual(d2); - }); - test(d.addMonths.name, () => { - const d1 = d.make(2018, d.Month.January, 1); - const d2 = d.make(2018, d.Month.Feb, 1); - expect(d.addMonths(1)(d1)).toEqual(d2); - }); - test(d.addDays.name, () => { - const d1 = d.make(2018, d.Month.January, 1); - const d2 = d.make(2018, d.Month.January, 2); - expect(d.addDays(1)(d1)).toEqual(d2); - }); - test(d.sub.name, () => { - const d1 = d.make(2018, d.Month.January, 1); - - expect(d.sub()(d1)).toEqual(d1); - expect(d.sub(1)(d1)).toEqual(d.make(2017, d.Month.Jan, 1)); - expect(d.sub(1, 1)(d1)).toEqual(d.make(2016, d.Month.December, 1)); - expect(d.sub(1, 1, 1)(d1)).toEqual(d.make(2016, d.Month.November, 30)); - }); - test(d.subYears.name, () => { - const d1 = d.make(2018, d.Month.January, 1); - const d2 = d.make(2017, d.Month.January, 1); - expect(d.subYears(1)(d1)).toEqual(d2); - }); - test(d.subMonths.name, () => { - const d1 = d.make(2018, d.Month.January, 1); - const d2 = d.make(2017, d.Month.December, 1); - expect(d.subMonths(1)(d1)).toEqual(d2); - }); - test(d.subDays.name, () => { - const d1 = d.make(2018, d.Month.January, 1); - const d2 = d.make(2017, d.Month.December, 31); - expect(d.subDays(1)(d1)).toEqual(d2); - }); - test(d.addYear.name, () => { - const d1 = d.make(2018, d.Month.January, 1); - const d2 = d.make(2019, d.Month.January, 1); - expect(d.addYear(d1)).toEqual(d2); - }); - test(d.addMonth.name, () => { - const d1 = d.make(2018, d.Month.January, 1); - const d2 = d.make(2018, d.Month.Feb, 1); - expect(d.addMonth(d1)).toEqual(d2); - }); - test(d.addDay.name, () => { - const d1 = d.make(2018, d.Month.January, 1); - const d2 = d.make(2018, d.Month.January, 2); - expect(d.addDay(d1)).toEqual(d2); - }); - test(d.subYear.name, () => { - const d1 = d.make(2018, d.Month.January, 1); - const d2 = d.make(2017, d.Month.January, 1); - expect(d.subYear(d1)).toEqual(d2); - }); - test(d.subMonth.name, () => { - const d1 = d.make(2018, d.Month.January, 1); - const d2 = d.make(2017, d.Month.December, 1); - expect(d.subMonth(d1)).toEqual(d2); - }); - test(d.subDay.name, () => { - const d1 = d.make(2018, d.Month.January, 1); - const d2 = d.make(2017, d.Month.December, 31); - expect(d.subDay(d1)).toEqual(d2); - }); - test(d.getYear.name, () => { - const d1 = d.make(2018, d.Month.January, 1); - expect(d.getYear(d1)).toBe(2018); - }); - test(d.getMonth.name, () => { - const d1 = d.make(2018, d.Month.January, 1); - expect(d.getMonth(d1)).toBe(d.Month.January); - }); - test(d.getDay.name, () => { - const d1 = d.make(2018, d.Month.January, 1); - expect(d.getDay(d1)).toBe(1); - }); - test(d.asc.name, () => { - const d1 = d.make(2018, d.Month.January, 1); - const d2 = d.make(2018, d.Month.January, 2); - expect(d.asc(d1, d2)).toBe(1); - expect(d.asc(d2, d1)).toBe(-1); - expect(d.asc(d2, d2)).toBe(0); - }); - test(d.desc.name, () => { - const d1 = d.make(2018, d.Month.January, 1); - const d2 = d.make(2018, d.Month.January, 2); - expect(d.desc(d1, d2)).toBe(-1); - expect(d.desc(d2, d1)).toBe(1); - expect(d.desc(d2, d2)).toBe(0); - }); - test(d.toString.name, () => { - const d1 = d.make(2018, d.Month.January, 1); - const d2 = d.make(2018, d.Month.Nov, 20); - const d3 = d.make(2018, d.Month.Oct, 10); - expect(d.toString(d1)).toBe(`2018-01-01`); - expect(d.toString(d2)).toBe(`2018-11-20`); - expect(d.toString(d3)).toBe(`2018-10-10`); - }); - test(d.fromstr.name, () => { - const d1 = d.make(2018, d.Month.January, 1); - const d2 = d.make(2018, d.Month.Nov, 20); - expect(d.fromstr(`2018-01-01`)).toEqual(d1); - expect(d.fromstr(`2018-11-20`)).toEqual(d2); - }); -}); \ No newline at end of file diff --git a/src/__tests__/either.test.ts b/src/__tests__/either.test.ts deleted file mode 100644 index b3bc4e5..0000000 --- a/src/__tests__/either.test.ts +++ /dev/null @@ -1,169 +0,0 @@ -import { describe, expect, test } from 'vitest'; -import * as e from '../either'; -import * as num from '../num'; -import * as str from '../str'; - -describe(`either`, () => { - test(`isLeft`, () => { - expect(e.isLeft([1, null])).toBe(true); - expect(e.isLeft([null, 2])).toBe(false); - expect(e.isLeft([null, null])).toBe(false); - }); - test(`isLeftOf`, () => { - const l = e.isLeftOf(num.guard); - expect(l([1, null])).toBe(true); - expect(l(["hello", null])).toBe(false); - expect(l([null, 2])).toBe(false); - expect(l([null, null])).toBe(false); - }); - test(`isRight`, () => { - expect(e.isRight([null, 2])).toBe(true); - expect(e.isRight([1, null])).toBe(false); - expect(e.isRight([null, null])).toBe(false); - }); - test(`isRightOf`, () => { - const r = e.isRightOf(num.guard); - expect(r([null, 2])).toBe(true); - expect(r([null, "hello"])).toBe(false); - expect(r([1, null])).toBe(false); - expect(r([null, null])).toBe(false); - }); - test(`fold`, () => { - const ml = num.uadd(10); - const mr = str.length; - const f = e.fold(ml, mr); - - expect(f(e.left(10))).toBe(20); - expect(f(e.right("hello"))).toBe(5); - expect(() => f([null, null] as any)).toThrow(); - }); - test(`guard`, () => { - expect(e.guard([1, null])).toBe(true); - expect(e.guard([null, 2])).toBe(true); - expect(e.guard([null, null])).toBe(false); - }); - test(`guardOf`, () => { - const g = e.guardOf(num.guard, str.guard); - expect(g([1, null])).toBe(true); - expect(g(["hello", null])).toBe(false); - expect(g([null, "hello"])).toBe(true); - expect(g([null, 2])).toBe(false); - expect(g([null, null])).toBe(false); - }); - test(`left`, () => { - expect(e.left(1)).toEqual([1, null]); - }); - test(`right`, () => { - expect(e.right(2)).toEqual([null, 2]); - }); - test(`cmp`, () => { - expect(e.cmp([1, null], [1, null])).toBe(0); - expect(e.cmp([1, null], [2, null])).toBe(-1); - expect(e.cmp([2, null], [1, null])).toBe(1); - expect(e.cmp([1, null], [null, null])).toBe(1); - expect(e.cmp([null, null], [1, null])).toBe(-1); - expect(e.cmp([null, 1], [1, null])).toBe(1); - expect(e.cmp([-1, null], [null, 1])).toBe(-1); - expect(e.cmp([null, 2], [null, 2])).toBe(0); - expect(e.cmp([null, 1], [null, 2])).toBe(-1); - expect(e.cmp([null, 2], [null, 1])).toBe(1); - expect(e.cmp([null, null], [null, 1])).toBe(-1); - }); - test(`eq`, () => { - expect(e.eq([1, null], [1, null])).toBe(true); - expect(e.eq([1, null], [2, null])).toBe(false); - expect(e.eq([2, null], [1, null])).toBe(false); - expect(e.eq([null, 1], [null, 2])).toBe(false); - expect(e.eq([null, 2], [null, 1])).toBe(false); - expect(e.eq([null, null], [null, null])).toBe(true); - }); - test(`filterLeft`, () => { - const f = e.filterLeft(num.gt(2)); - expect(f([1, null])).toEqual([null, null]); - expect(f([3, null])).toEqual([3, null]); - expect(f([null, 2])).toEqual([null, 2]); - expect(f([null, null])).toEqual([null, null]); - }); - test(`filterRight`, () => { - const f = e.filterRight(num.gt(2)); - expect(f([1, null])).toEqual([1, null]); - expect(f([null, 2])).toEqual([null, null]); - expect(f([null, 3])).toEqual([null, 3]); - expect(f([null, null])).toEqual([null, null]); - }); - test(`mapLeft`, () => { - const f = e.mapLeft(num.uadd(1)); - expect(f([1, null])).toEqual([2, null]); - expect(f([null, 2])).toEqual([null, null]); - expect(f([null, null])).toEqual([null, null]); - }); - test(`mapLeftOr`, () => { - const f = e.mapLeftOr(0, num.uadd(1)); - expect(f([1, null])).toEqual([2, null]); - expect(f([null, 2])).toEqual([0, null]); - expect(f([null, null])).toEqual([0, null]); - }); - test(`mapLeftOrElse`, () => { - const f = e.mapLeftOrElse(() => 0, num.uadd(1)); - expect(f([1, null])).toEqual([2, null]); - expect(f([null, 2])).toEqual([0, null]); - expect(f([null, null])).toEqual([0, null]); - }); - test(`mapRight`, () => { - const f = e.mapRight(num.uadd(1)); - expect(f([1, null])).toEqual([null, null]); - expect(f([null, 2])).toEqual([null, 3]); - expect(f([null, null])).toEqual([null, null]); - }); - test(`mapRightOr`, () => { - const f = e.mapRightOr(0, num.uadd(1)); - expect(f([1, null])).toEqual([null, 0]); - expect(f([null, 2])).toEqual([null, 3]); - expect(f([null, null])).toEqual([null, 0]); - }); - test(`mapRightOrElse`, () => { - const f = e.mapRightOrElse(() => 0, num.uadd(1)); - expect(f([1, null])).toEqual([null, 0]); - expect(f([null, 2])).toEqual([null, 3]); - expect(f([null, null])).toEqual([null, 0]); - }); - test(`swap`, () => { - expect(e.swap([1, null])).toEqual([null, 1]); - expect(e.swap([null, 2])).toEqual([2, null]); - expect(e.swap([null, null])).toEqual([null, null]); - }); - test(`unwrapLeft`, () => { - expect(e.unwrapLeft([1, null])).toBe(1); - expect(() => e.unwrapLeft([null, 2])).toThrow(); - expect(() => e.unwrapLeft([null, null])).toThrow(); - }); - test(`unwrapLeftOr`, () => { - const u = e.unwrapLeftOr(0); - expect(u([1, null])).toBe(1); - expect(u([null, 2])).toBe(0); - expect(u([null, null])).toBe(0); - }); - test(`unwrapLeftOrElse`, () => { - const u = e.unwrapLeftOrElse(() => 0); - expect(u([1, null])).toBe(1); - expect(u([null, 2])).toBe(0); - expect(u([null, null])).toBe(0); - }); - test(`unwrapRight`, () => { - expect(() => e.unwrapRight([1, null])).toThrow(); - expect(e.unwrapRight([null, 2])).toBe(2); - expect(() => e.unwrapRight([null, null])).toThrow(); - }); - test(`unwrapRightOr`, () => { - const u = e.unwrapRightOr(0); - expect(u([1, null])).toBe(0); - expect(u([null, 2])).toBe(2); - expect(u([null, null])).toBe(0); - }); - test(`unwrapRightOrElse`, () => { - const u = e.unwrapRightOrElse(() => 0); - expect(u([1, null])).toBe(0); - expect(u([null, 2])).toBe(2); - expect(u([null, null])).toBe(0); - }); -}); \ No newline at end of file diff --git a/src/__tests__/function.test.ts b/src/__tests__/function.test.ts deleted file mode 100644 index 7f2ac2d..0000000 --- a/src/__tests__/function.test.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { describe, expect, test } from 'vitest'; -import { Arr, Str } from '..'; -import * as fx from '../function'; -import * as num from '../num'; -import { pipe } from '../pipe'; - -describe(``, () => { - test(`bind`, () => { - const bound = fx.bind(num.badd, 10, 20); - expect(bound()).toBe(30); - }); - test(`call`, () => { - expect(fx.call(num.badd, 1, 2)).toBe(3); - expect(fx.call(num.uadd, 1)(2)).toBe(3); - }); - test(`cmp`, () => { - expect(fx.cmp(num.uadd, num.badd)).toBe(-1); - expect(fx.cmp(num.badd, num.uadd)).toBe(1); - expect(fx.cmp(num.badd, num.badd)).toBe(0); - expect(fx.cmp(num.badd, num.bmod)).toBe(-1); - expect(fx.cmp(num.bmod, num.badd)).toBe(1); - }); - test(`eq`, () => { - expect(fx.eq(num.uadd, num.badd)).toBe(false); - expect(fx.eq(num.badd, num.uadd)).toBe(false); - expect(fx.eq(num.badd, num.badd)).toBe(true); - expect(fx.eq(num.badd, num.bmod)).toBe(false); - expect(fx.eq(num.bmod, num.badd)).toBe(false); - }); - test(`guard`, () => { - expect(fx.guard(10)).toBeFalsy(); - expect(fx.guard(new Function())).toBeTruthy(); - expect(fx.guard(function () { })).toBeTruthy(); - expect(fx.guard(() => { })).toBeTruthy(); - }); - test(`curry2`, () => { - const fn = num.badd; - const c = fx.curry2(fn); - expect(c(1)(2)).toBe(3); - }); - test(`curry3`, () => { - const fn = (a: number, b: number, c: number) => a + b + c; - const c = fx.curry3(fn); - expect(c(1)(2)(3)).toBe(6); - }); - test(`curry4`, () => { - const fn = (a: number, b: number, c: number, d: number) => a + b + c + d; - expect(fx.curry4(fn)(1)(2)(3)(4)).toBe(10); - }); - test(`curry5`, () => { - const fn = (a: number, b: number, c: number, d: number, e: number) => a + b + c + d + e; - expect(fx.curry5(fn)(1)(2)(3)(4)(5)).toBe(15); - }); - test(`curry6`, () => { - const fn = (a: number, b: number, c: number, d: number, e: number, f: number) => a + b + c + d + e + f; - expect(fx.curry6(fn)(1)(2)(3)(4)(5)(6)).toBe(21); - }); - test(`curry7`, () => { - const fn = (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => a + b + c + d + e + f + g; - expect(fx.curry7(fn)(1)(2)(3)(4)(5)(6)(7)).toBe(28); - }); - test(`curry8`, () => { - const fn = (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number) => a + b + c + d + e + f + g + h; - expect(fx.curry8(fn)(1)(2)(3)(4)(5)(6)(7)(8)).toBe(36); - }); - test(`curry9`, () => { - const fn = (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number) => a + b + c + d + e + f + g + h + i; - expect(fx.curry9(fn)(1)(2)(3)(4)(5)(6)(7)(8)(9)).toBe(45); - }); - test(`curry10`, () => { - const fn = (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, l: number) => a + b + c + d + e + f + g + h + i + l; - expect(fx.curry10(fn)(1)(2)(3)(4)(5)(6)(7)(8)(9)(10)).toBe(55); - }); - test(`name`, () => { - const lorem = () => { }; - expect(fx.name(lorem)).toBe(`lorem`); - }); - - test(fx.pass.name, () => { - expect(fx.pass(10)).toBe(10); - }); - - test(fx.map.name, () => { - const chars = Str.chars; - const upperchars = pipe(chars, Arr.map(Str.upper)); - const m = fx.map(chars, upperchars); - - expect(m('abc')).toEqual([['a', 'b', 'c'], ['A', 'B', 'C']]); - }); -}); \ No newline at end of file diff --git a/src/__tests__/maybe.test.ts b/src/__tests__/maybe.test.ts deleted file mode 100644 index 3ac9c20..0000000 --- a/src/__tests__/maybe.test.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { describe, expect, test } from 'vitest'; -import * as m from '../maybe'; - -describe(`maybe`, () => { - test(`isjust or isnothing`, () => { - expect(m.isJust(10)).toBe(true); - expect(m.isJust(true)).toBe(true); - expect(m.isJust(`hello world`)).toBe(true); - expect(m.isJust(null)).toBe(false); - expect(m.isJust(false)).toBe(false); - expect(m.isJust(undefined)).toBe(false); - expect(m.isNothing(10)).toBe(false); - expect(m.isNothing(null)).toBe(true); - expect(m.isNothing(``)).toBe(true); - expect(m.isNothing(undefined)).toBe(true); - expect(m.isNothing(0)).toBe(true); - }); - - test(`isMaybeOf`, () => { - const g = (x: unknown): x is number => typeof x === 'number'; - const gof = m.isMaybeOf(g); - - expect(gof(10)).toBe(true); - expect(gof(new Date())).toBe(false); - expect(gof(null)).toBe(true); - expect(gof(undefined)).toBe(true); - expect(gof(false)).toBe(true); - expect(gof(0)).toBe(true); - expect(gof('')).toBe(true); - expect(gof('hello')).toBe(false); - }); - - test(`cmp`, () => { - expect(m.cmp(null, null)).toBe(0); - expect(m.cmp(undefined, undefined)).toBe(0); - expect(m.cmp(0, 0)).toBe(0); - expect(m.cmp(0, 1)).toBe(-1); - expect(m.cmp(1, 0)).toBe(1); - expect(m.cmp(1, 1)).toBe(0); - - const a = `hello`; - const b = `world`; - - expect(m.cmp(a, a)).toBe(0); - expect(m.cmp(a, b)).toBe(-1); - expect(m.cmp(b, a)).toBe(1); - }); - - test(`eq`, () => { - expect(m.eq(null, null)).toBe(true); - expect(m.eq(undefined, undefined)).toBe(true); - expect(m.eq(undefined, null)).toBe(true); - expect(m.eq(null, undefined)).toBe(true); - expect(m.eq(0, undefined)).toBe(true); - expect(m.eq(0, false as any)).toBe(true); - expect(m.eq(0, 0)).toBe(true); - expect(m.eq(0, 1)).toBe(false); - expect(m.eq(1, 0)).toBe(false); - expect(m.eq(1, 1)).toBe(true); - - const a = `hello`; - const b = `world`; - - expect(m.eq(a, a)).toBe(true); - expect(m.eq(a, b)).toBe(false); - expect(m.eq(b, a)).toBe(false); - }); - - test(`filter`, () => { - const p = (a: string) => a.length > 4; - const f = m.filter(p); - - expect(f(null)).toBe(null); - expect(f(undefined)).toBe(null); - expect(f(``)).toBe(null); - expect(f(`hello`)).toBe(`hello`); - expect(f(`hello world`)).toBe(`hello world`); - }); - - test(`map`, () => { - const f = (a: string) => a.length; - const nothing1 = null as m.nothing; - const nothing2 = undefined as m.nothing; - const or = 0 as m.maybe; - const orElse = () => or; - - const map = m.map(f); - const mapOr = m.mapOr(or, f); - const mapOrElse = m.mapOrElse(orElse, f); - - expect(map(nothing1)).toBe(null); - expect(map(nothing2)).toBe(null); - expect(map(``)).toBe(null); - expect(map(`hello`)).toBe(5); - expect(map(`hello world`)).toBe(11); - - expect(mapOr(nothing1)).toBe(0); - expect(mapOr(nothing2)).toBe(0); - expect(mapOr(``)).toBe(0); - expect(mapOr(`hello`)).toBe(5); - expect(mapOr(`hello world`)).toBe(11); - - expect(mapOrElse(nothing1)).toBe(0); - expect(mapOrElse(nothing2)).toBe(0); - expect(mapOrElse(``)).toBe(0); - expect(mapOrElse(`hello`)).toBe(5); - expect(mapOrElse(`hello world`)).toBe(11); - }); - - test(`unwrappables`, () => { - const nothing = null as m.nothing; - const just = 10 as m.maybe; - const or = 0 as m.maybe; - const orElse = () => or; - - expect(() => m.unwrap(nothing)).toThrow(); - expect(m.unwrap(just)).toBe(10); - - expect(m.unwrapOr(or)(nothing)).toBe(0); - expect(m.unwrapOr(or)(just)).toBe(10); - - expect(m.unwrapOrElse(orElse)(nothing)).toBe(0); - expect(m.unwrapOrElse(orElse)(just)).toBe(10); - }); -}); diff --git a/src/__tests__/num.test.ts b/src/__tests__/num.test.ts deleted file mode 100644 index 653cc78..0000000 --- a/src/__tests__/num.test.ts +++ /dev/null @@ -1,141 +0,0 @@ -import { describe, expect, test } from 'vitest'; -import * as n from '../num'; - -describe('num', () => { - test(`guard`, () => { - expect(n.guard(10)).toBe(true); - expect(n.guard('10')).toBe(false); - }); - test(`cmp`, () => { - expect(n.cmp(10, 10)).toBe(0); - expect(n.cmp(10, 20)).toBe(-1); - expect(n.cmp(20, 10)).toBe(1); - }); - test(`isEven`, () => { - expect(n.isEven(10)).toBe(true); - expect(n.isEven(20)).toBe(true); - expect(n.isEven(11)).toBe(false); - }); - test(`isOdd`, () => { - expect(n.isOdd(10)).toBe(false); - expect(n.isOdd(20)).toBe(false); - expect(n.isOdd(11)).toBe(true); - }); - test(`sortasc`, () => { - const array = [10, 20, 30, 40, 50]; - expect(expect.arrayContaining(array.sort(n.asc))).toEqual([10, 20, 30, 40, 50]); - }); - test(`sortdesc`, () => { - const array = [10, 20, 30, 40, 50]; - expect(expect.arrayContaining(array.sort(n.desc))).toEqual([50, 40, 30, 20, 10]); - }); - test(`eq`, () => { - expect(n.eq(10)(10)).toBe(true); - expect(n.eq(10)(20)).toBe(false); - }); - test(`ge`, () => { - expect(n.ge(10)(10)).toBe(true); - expect(n.ge(12)(10)).toBe(false); - expect(n.ge(10)(20)).toBe(true); - }); - test(`gt`, () => { - expect(n.gt(10)(10)).toBe(false); - expect(n.gt(12)(10)).toBe(false); - expect(n.gt(10)(20)).toBe(true); - }); - test(`le`, () => { - expect(n.le(10)(10)).toBe(true); - expect(n.le(12)(10)).toBe(true); - expect(n.le(10)(20)).toBe(false); - }); - test(`lt`, () => { - expect(n.lt(10)(10)).toBe(false); - expect(n.lt(12)(10)).toBe(true); - expect(n.lt(10)(20)).toBe(false); - }); - test(`ne`, () => { - expect(n.ne(10)(10)).toBe(false); - expect(n.ne(12)(10)).toBe(true); - expect(n.ne(10)(20)).toBe(true); - }); - test(`toExponential`, () => { - expect(n.toExponential(10)).toBe('1e+1'); - }); - test(`toExponentialF`, () => { - expect(n.toExponentialF(2)(2)).toBe('2.00e+0'); - }); - test(`toFixed`, () => { - expect(n.toFixed(10)).toBe('10.00'); - }); - test(`toFixedF`, () => { - expect(n.toFixedF(3)(2)).toBe('2.000'); - }); - test(`toPrecision`, () => { - expect(n.toPrecision(10)).toBe('10'); - }); - test(`toPrecisionP`, () => { - expect(n.toPrecisionP(3)(2)).toBe('2.00'); - }); - test(`toString`, () => { - expect(n.toString(10)).toBe('10'); - }); - test(`toStringR`, () => { - expect(n.toStringR(32)(30)).toBe('u'); - }); - test(`uadd`, () => { - expect(n.uadd(10)(20)).toBe(30); - }); - test(`udiv`, () => { - expect(n.udiv(10)(20)).toBe(2); - }); - test(`umod`, () => { - expect(n.umod(10)(20)).toBe(0); - }); - test(`umul`, () => { - expect(n.umul(10)(20)).toBe(200); - }); - test(`upow`, () => { - expect(n.upow(2)(10)).toBe(100); - }); - test(`urand`, () => { - expect(n.urand(10)(20)).toBeGreaterThanOrEqual(10); - expect(n.urand(10)(20)).toBeLessThan(20); - }); - test(`uroot`, () => { - expect(n.uroot(2)(10)).toBe(3.1622776601683795); - }); - test(`usub`, () => { - expect(n.usub(10)(20)).toBe(10); - }); - test(`badd`, () => { - expect(n.badd(10, 20)).toBe(30); - }); - test(`bdiv`, () => { - expect(n.bdiv(10, 20)).toBe(0.5); - }); - test(`bmod`, () => { - expect(n.bmod(10, 20)).toBe(10); - }); - test(`bmul`, () => { - expect(n.bmul(10, 20)).toBe(200); - }); - test(`bpow`, () => { - expect(n.bpow(2, 10)).toBe(1024); - }); - test(`brand`, () => { - expect(n.brand(10, 20)).toBeGreaterThanOrEqual(10); - expect(n.brand(10, 20)).toBeLessThan(20); - }); - test(`broot`, () => { - expect(n.broot(2, 10)).toBe(1.0717734625362931); - }); - test(`bsub`, () => { - expect(n.bsub(10, 20)).toBe(-10); - }); - test(`int`, () => { - expect(n.int(10.5)).toBe(10); - expect(n.int(10.1)).toBe(10); - expect(n.int(10.9)).toBe(10); - expect(n.int(10)).toBe(10); - }); -}); \ No newline at end of file diff --git a/src/__tests__/obj.test.ts b/src/__tests__/obj.test.ts deleted file mode 100644 index 77a80fb..0000000 --- a/src/__tests__/obj.test.ts +++ /dev/null @@ -1,170 +0,0 @@ -import { describe, expect, test } from 'vitest'; -import * as bool from '../bool'; -import * as num from '../num'; -import * as obj from '../obj'; -import * as o from '../option'; -import * as str from '../str'; - -describe('obj', () => { - test(obj.cmp.name, () => { - const a = { a: 1, b: { c: 2 } }; - const b = { a: 1, b: { c: 3 } }; - const c = { a: 1, b: { c: 2 } }; - const d = { a: 1, b: { c: 2, d: 3 } }; - const e = { a: 1, b: { c: 3, d: 3, e: 4 } }; - - expect(obj.cmp(a, b)).toBe(-1); - expect(obj.cmp(b, a)).toBe(1); - expect(obj.cmp(a, c)).toBe(0); - expect(obj.cmp(a, d)).toBe(1); - expect(obj.cmp(e, c)).toBe(-1); - }); - - test(obj.entries.name, () => { - const a = { a: 1, b: { c: 2 } }; - - expect(expect.arrayContaining(obj.entries(a))).toEqual([['a', 1], ['b', { c: 2 }]]); - }); - - test(obj.flat.name, () => { - const a = { a: 1, b: { c: 2 } }; - - expect(obj.flat(a)).toEqual({ a: 1, 'b.c': 2 }); - }); - - test(obj.get.name, () => { - const a = { a: 1, b: { c: 2 } }; - - expect(obj.get('a')(a)).toEqual(1); - expect(obj.get('b')(a)).toEqual({ c: 2 }); - expect(obj.get('c')(a)).toBeNull(); - }); - - test(obj.guard.name, () => { - expect(obj.guard({})).toBeTruthy(); - expect(obj.guard(null)).toBeFalsy(); - expect(obj.guard(undefined)).toBeFalsy(); - }); - - test(obj.guardOf.name, () => { - const isshape = obj.guardOf({ - a: str.guard, - b: num.guard, - c: bool.guard - }); - const isshapeb = obj.guardOf({ - a: isshape, - b: bool.guard, - }); - const isshapec = obj.guardOf({ - b: bool.guard, - c: o.isOptionOf(num.guard), - }); - const isshaped = obj.guardOf({ - a: { - b: num.guard - } - }); - - expect(isshape({ a: `foo`, b: 1, c: true })).toBe(true); - expect(isshape({ a: `foo`, b: false, c: 1 })).toBe(false); - expect(isshape(0)).toBe(false); - expect(isshapeb({ a: { a: `foo`, b: 1, c: true }, b: true })).toBe(true); - expect(isshapeb({ a: { a: `foo`, b: false, c: 1 }, b: true })).toBe(false); - expect(isshapeb({ a: 2, b: false })).toBe(false); - expect(isshapec({ b: true, c: 1 })).toBe(true); - expect(isshapec({ b: true })).toBe(true); - expect(isshapec({ b: 2, c: false })).toBe(false); - expect(isshapec({ b: 2 })).toBe(false); - expect(isshaped({ a: { b: 1 } })).toBe(true); - expect(isshaped({ a: { b: false } })).toBe(false); - }); - - test(obj.haskey.name, () => { - expect(obj.haskey('a')({ a: 1 })).toBeTruthy(); - expect(obj.haskey('b')({ a: 1 })).toBeFalsy(); - }); - - test(obj.haskeyOf.name, () => { - expect(obj.haskeyOf('a', num.guard)({ a: 1 })).toBeTruthy(); - expect(obj.haskeyOf('a', str.guard)({ a: 1 })).toBeFalsy(); - expect(obj.haskeyOf('b', num.guard)({ a: 1 })).toBeFalsy(); - }); - - test(obj.keys.name, () => { - expect(expect.arrayContaining(obj.keys({ a: 1, b: { c: 2 } }))).toEqual(['a', 'b']); - }); - - test(obj.omit.name, () => { - expect(obj.omit(['a'])({ a: 1, b: { c: 2 } })).toEqual({ b: { c: 2 } }); - }); - - test(obj.pick.name, () => { - expect(obj.pick(['a'])({ a: 1, b: { c: 2 } })).toEqual({ a: 1 }); - }); - - test(obj.values.name, () => { - const a = { a: 1, b: { c: 2 } }; - const v = obj.values(a); - expect(expect.arrayContaining(v)).toEqual([1, { c: 2 }]); - }); - - test(obj.assign.name, () => { - const a = { a: 1, b: 2 }; - const b = { b: 3, c: 4 }; - const c = { c: 5, d: 6 }; - - expect(obj.assign(a, b, c)).toEqual({ a: 1, b: 3, c: 5, d: 6 }); - }); - - test(obj.size.name, () => { - const a = { a: 1, b: { c: 2 } }; - const b = {}; - - expect(obj.size(a)).toBe(2); - expect(obj.size(b)).toBe(0); - }); - - test(obj.defineProperty.name, () => { - const a = { a: 1, b: 2 }; - - obj.defineProperty(a, 'c', { value: 3, enumerable: true, configurable: true, writable: true }); - expect((a as any).c).toBe(3); - (a as any).c = 4; - expect((a as any).c).toBe(4); - }); - - test(obj.freeze.name, () => { - const a = { a: 1, b: 2 }; - obj.freeze(a); - - expect(() => { a.a = 100; }).toThrow(); - }); - - test(obj.fromEntries.name, () => { - const fromEntries = obj.fromEntries([ - ['a', 1], - ['b', 2] - ]); - - expect(fromEntries).toEqual({ a: 1, b: 2 }); - }); - - test(obj.map.name, () => { - const map = obj.map(num.umul(2)); - const map2 = obj.map((a: number | string | boolean) => { - switch (typeof a) { - case 'number': return a * 2; - case 'string': return a + a; - case 'boolean': return !a; - } - }); - expect(map({ a: 1, b: 2 })).toEqual({ a: 2, b: 4 }); - expect(map2({ a: 2, b: 'hello', c: true })).toEqual({ a: 4, b: 'hellohello', c: false }); - }); - - test(obj.mapkeys.name, () => { - const map = obj.mapkeys(str.upper); - expect(map({ a: 1, b: 2 })).toEqual({ A: 1, B: 2 }); - }); -}); \ No newline at end of file diff --git a/src/__tests__/option.test.ts b/src/__tests__/option.test.ts deleted file mode 100644 index 0d207b4..0000000 --- a/src/__tests__/option.test.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { describe, expect, test } from 'vitest'; -import * as o from '../option'; - -describe(`option`, () => { - test(`checks if is some or none`, () => { - expect(o.isNone(null)).toBe(true); - expect(o.isNone(undefined)).toBe(true); - expect(o.isNone(0)).toBe(false); - expect(o.isNone('')).toBe(false); - expect(o.isSome(false)).toBe(true); - expect(o.isSome('')).toBe(true); - expect(o.isSome(0)).toBe(true); - expect(o.isSome(null)).toBe(false); - expect(o.isSome(undefined)).toBe(false); - }); - - test(`isOptionOf`, () => { - const g = (x: unknown): x is number => typeof x === 'number'; - const gof = o.isOptionOf(g); - - expect(gof(10)).toBe(true); - expect(gof(Error)).toBe(false); - expect(gof(null)).toBe(true); - expect(gof(undefined)).toBe(true); - expect(gof('hello')).toBe(false); - }); - - test(`mappables`, () => { - const mapfn = (s: string) => s.length; - const map = o.map(mapfn); - const mapor = o.mapOr(0, mapfn); - const maporelse = o.mapOrElse(() => 0, mapfn); - - const some = 'hello'; - - expect(map(null)).toBe(null); - expect(map(undefined)).toBe(undefined); - expect(map(some)).toBe(5); - expect(mapor(null)).toBe(0); - expect(mapor(undefined)).toBe(0); - expect(mapor(some)).toBe(5); - expect(maporelse(null)).toBe(0); - expect(maporelse(undefined)).toBe(0); - expect(maporelse(some)).toBe(5); - }); - - test(`comparables`, () => { - expect(o.cmp(null, null)).toBe(0); - expect(o.cmp(undefined, undefined)).toBe(0); - expect(o.cmp(0, 0)).toBe(0); - expect(o.cmp(0, 1)).toBe(-1); - expect(o.cmp(1, 0)).toBe(1); - expect(o.cmp(1, 1)).toBe(0); - - const a = `hello`; - const b = `world`; - - expect(o.cmp(a, a)).toBe(0); - expect(o.cmp(a, b)).toBe(-1); - expect(o.cmp(b, a)).toBe(1); - - const x = 1; - const y = null; - const z = undefined; - - expect(o.cmp(x, y)).toBe(1); - expect(o.cmp(x, z)).toBe(1); - expect(o.cmp(y, z)).toBe(0); - expect(o.cmp(x, x)).toBe(0); - expect(o.cmp(z, x)).toBe(-1); - }); - - test(`equatables`, () => { - expect(o.eq(null, undefined)).toBe(true); - expect(o.eq(null, null)).toBe(true); - expect(o.eq(undefined, null)).toBe(true); - expect(o.eq(undefined, undefined)).toBe(true); - expect(o.eq(0, 0)).toBe(true); - expect(o.eq(0, 1)).toBe(false); - expect(o.eq(1, 0)).toBe(false); - }); - - test(`filterables`, () => { - const p = (value: number) => value > 0; - const f = o.filter(p); - - expect(f(10)).toBe(10); - expect(f(-10)).toBe(null); - expect(f(0)).toBe(null); - }); - - test(`mappable types`, () => { - const mapfn1 = (s: string) => s.length; - const mapfn2 = (s: number) => `count:${s}`; - const map1 = o.map(mapfn1); - const map2 = o.map(mapfn2); - const none = null as o.none; - - expect(map2(map1(''))).toBe(`count:0`); - expect(map2(map1('hello'))).toBe(`count:5`); - expect(map2(map1(none))).toBe(none); - expect(map2(map1(undefined))).toBe(undefined); - }); - - test(`unwrappables`, () => { - const none = null as o.none; - const or = 0 as o.option; - - expect(() => o.unwrap(none)).toThrowError(); - expect(o.unwrapOr(or)(none)).toBe(0); - expect(o.unwrapOrElse(() => or)(none)).toBe(0); - - expect(() => o.unwrap(undefined)).toThrowError(); - expect(o.unwrapOr(or)(undefined)).toBe(0); - expect(o.unwrapOrElse(() => or)(undefined)).toBe(0); - - expect(() => o.unwrap(10)).not.toThrowError(); - expect(o.unwrap(10)).toBe(10); - expect(o.unwrapOr(0)(10)).toBe(10); - expect(o.unwrapOrElse(() => 0)(10)).toBe(10); - }); -}); diff --git a/src/__tests__/pipe.async.test.ts b/src/__tests__/pipe.async.test.ts deleted file mode 100644 index 6f1b341..0000000 --- a/src/__tests__/pipe.async.test.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { describe, expect, test } from 'vitest'; -import * as m from '../maybe'; -import * as n from '../num'; -import { pipeasync, toasync } from '../pipe.async'; - -describe(`pipe.async`, () => { - test(`async version of pipe`, async () => { - const wait = (ms: number) => (arg: string): Promise => new Promise(resolve => setTimeout(() => resolve(arg), ms)); - - const piped = pipeasync( - toasync(n.uadd(10)), - toasync(n.umul(2)), - toasync(n.isEven), - toasync(m.mapOrElse(() => 'odd', () => 'even')), - wait(10), - ); - - expect(await piped(2)).toBe(`even`); - expect(await piped(1)).toBe(`even`); - expect(await piped(1.3)).toBe(`odd`); - }); -}); \ No newline at end of file diff --git a/src/__tests__/pipe.test.ts b/src/__tests__/pipe.test.ts deleted file mode 100644 index f2dab9a..0000000 --- a/src/__tests__/pipe.test.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { describe, expect, test } from 'vitest'; -import { Number, Option } from '..'; -import * as m from '../maybe'; -import * as n from '../num'; -import { pipe } from '../pipe'; - -describe(`pipe`, () => { - test(`pipe example`, () => { - const piped = pipe( - n.uadd(10), - n.umul(2), - n.isEven, - m.mapOrElse(() => 'odd', () => 'even'), - ); - expect(piped(2)).toBe(`even`); - expect(piped(1)).toBe(`even`); - expect(piped(1.3)).toBe(`odd`); - }); - - test(`o(n) pipe`, () => { - const f = Number.uadd(1); - // @ts-ignore - const p0 = pipe(); - const p1 = pipe(f); - const p2 = pipe(f, f); - const p3 = pipe(f, f, f); - const p4 = pipe(f, f, f, f); - const p5 = pipe(f, f, f, f, f); - const p6 = pipe(f, f, f, f, f, f); - const p7 = pipe(f, f, f, f, f, f, f); - const p8 = pipe(f, f, f, f, f, f, f, f); - const p9 = pipe(f, f, f, f, f, f, f, f, f); - const p10 = pipe(f, f, f, f, f, f, f, f, f, f); - const p11 = pipe(f, f, f, f, f, f, f, f, f, f, f); - - expect(p0(0)).toBe(0); - expect(p1(0)).toBe(1); - expect(p2(0)).toBe(2); - expect(p3(0)).toBe(3); - expect(p4(0)).toBe(4); - expect(p5(0)).toBe(5); - expect(p6(0)).toBe(6); - expect(p7(0)).toBe(7); - expect(p8(0)).toBe(8); - expect(p9(0)).toBe(9); - expect(p10(0)).toBe(10); - expect(p11(0)).toBe(11); - }); - - test(`complex scenario`, () => { - const optadd = Option.map(Number.uadd(1)); - const optfilter = Option.filter(Number.isEven); - const optmultiply = Option.map(Number.umul(2)); - - const piped = pipe(optadd, optfilter, optmultiply); - - expect(piped(0)).toBe(null); - expect(piped(1)).toBe(4); - expect(piped(2)).toBe(null); - expect(piped(3)).toBe(8); - }); -}); diff --git a/src/__tests__/predicate.test.ts b/src/__tests__/predicate.test.ts deleted file mode 100644 index 7b83b10..0000000 --- a/src/__tests__/predicate.test.ts +++ /dev/null @@ -1,155 +0,0 @@ -import { describe, expect, test } from 'vitest'; -import { Number } from '..'; -import * as p from '../predicate'; - -describe(`predicate`, () => { - test(`and`, () => { - const p1 = (a: string) => a.length > 4; - const p2 = (a: string) => a.length < 10; - const f = p.and(p1, p2); - - expect(f(``)).toBe(false); - expect(f(`hello`)).toBe(true); - expect(f(`hello world`)).toBe(false); - }); - - test(`eq`, () => { - const f = p.eq(`hello`); - - expect(f(``)).toBe(false); - expect(f(`hello`)).toBe(true); - expect(f(`hello world`)).toBe(false); - }); - - test(`invert`, () => { - const f = p.invert(p.eq(`hello`)); - - expect(f(``)).toBe(true); - expect(f(`hello`)).toBe(false); - expect(f(`hello world`)).toBe(true); - }); - - test(`neq`, () => { - const f = p.neq(10); - - expect(f(5)).toBe(true); - expect(f(6)).toBe(true); - expect(f(10)).toBe(false); - expect(f(11)).toBe(true); - }); - - test(`noneof`, () => { - const f = p.noneof(p.eq(10), p.eq(11)); - - expect(f(5)).toBe(true); - expect(f(6)).toBe(true); - expect(f(10)).toBe(false); - expect(f(11)).toBe(false); - }); - - test(`or`, () => { - const f = p.or(p.eq(10), p.eq(11)); - - expect(f(5)).toBe(false); - expect(f(6)).toBe(false); - expect(f(10)).toBe(true); - expect(f(11)).toBe(true); - }); - - test(`linearity (and)`, () => { - const and0 = p.and(); - const and1 = p.and(Number.gt(0)); - const and2 = p.and(Number.gt(0), Number.gt(1)); - const and3 = p.and(Number.gt(0), Number.gt(1), Number.gt(2)); - const and4 = p.and(Number.gt(0), Number.gt(1), Number.gt(2), Number.gt(3)); - const and5 = p.and(Number.gt(0), Number.gt(1), Number.gt(2), Number.gt(3), Number.gt(4)); - const and6 = p.and(Number.gt(0), Number.gt(1), Number.gt(2), Number.gt(3), Number.gt(4), Number.gt(5)); - const and7 = p.and(Number.gt(0), Number.gt(1), Number.gt(2), Number.gt(3), Number.gt(4), Number.gt(5), Number.gt(6)); - const and8 = p.and(Number.gt(0), Number.gt(1), Number.gt(2), Number.gt(3), Number.gt(4), Number.gt(5), Number.gt(6), Number.gt(7)); - const and9 = p.and(Number.gt(0), Number.gt(1), Number.gt(2), Number.gt(3), Number.gt(4), Number.gt(5), Number.gt(6), Number.gt(7), Number.gt(8)); - const and10 = p.and(Number.gt(0), Number.gt(1), Number.gt(2), Number.gt(3), Number.gt(4), Number.gt(5), Number.gt(6), Number.gt(7), Number.gt(8), Number.gt(9)); - const and11 = p.and(Number.gt(0), Number.gt(1), Number.gt(2), Number.gt(3), Number.gt(4), Number.gt(5), Number.gt(6), Number.gt(7), Number.gt(8), Number.gt(9), Number.gt(10)); - - expect(and0(0)).toBe(true); - expect(and1(1)).toBe(true); - expect(and2(2)).toBe(true); - expect(and3(3)).toBe(true); - expect(and4(4)).toBe(true); - expect(and5(5)).toBe(true); - expect(and6(6)).toBe(true); - expect(and7(7)).toBe(true); - expect(and8(8)).toBe(true); - expect(and9(9)).toBe(true); - expect(and10(10)).toBe(true); - expect(and11(11)).toBe(true); - expect(and0(-1)).toBe(true); - expect(and1(0)).toBe(false); - expect(and2(1)).toBe(false); - expect(and3(2)).toBe(false); - expect(and4(3)).toBe(false); - expect(and5(4)).toBe(false); - expect(and6(5)).toBe(false); - expect(and7(6)).toBe(false); - expect(and8(7)).toBe(false); - expect(and9(8)).toBe(false); - expect(and10(9)).toBe(false); - expect(and11(10)).toBe(false); - }); - - test(`linearity (noneof)`, () => { - const noneof0 = p.noneof(); - const noneof1 = p.noneof(Number.gt(10)); - const noneof2 = p.noneof(Number.gt(10), Number.lt(0)); - const noneof3 = p.noneof(Number.gt(10), Number.lt(0), Number.lt(-9)); - const noneof4 = p.noneof(Number.gt(10), Number.lt(0), Number.lt(-9), Number.gt(9)); - const noneof5 = p.noneof(Number.gt(10), Number.lt(0), Number.lt(-9), Number.gt(9), Number.gt(8)); - const noneof6 = p.noneof(Number.gt(10), Number.lt(0), Number.lt(-9), Number.gt(9), Number.gt(8), Number.gt(7)); - const noneof7 = p.noneof(Number.gt(10), Number.lt(0), Number.lt(-9), Number.gt(9), Number.gt(8), Number.gt(7), Number.gt(6)); - const noneof8 = p.noneof(Number.gt(10), Number.lt(0), Number.lt(-9), Number.gt(9), Number.gt(8), Number.gt(7), Number.gt(6), Number.gt(5)); - const noneof9 = p.noneof(Number.gt(10), Number.lt(0), Number.lt(-9), Number.gt(9), Number.gt(8), Number.gt(7), Number.gt(6), Number.gt(5), Number.gt(4)); - const noneof10 = p.noneof(Number.gt(10), Number.lt(0), Number.lt(-9), Number.gt(9), Number.gt(8), Number.gt(7), Number.gt(6), Number.gt(5), Number.gt(4), Number.gt(3)); - const noneof11 = p.noneof(Number.gt(10), Number.lt(0), Number.lt(-9), Number.gt(9), Number.gt(8), Number.gt(7), Number.gt(6), Number.gt(5), Number.gt(4), Number.gt(3), Number.gt(2)); - - expect(noneof0(0)).toBe(true); - expect(noneof1(1)).toBe(true); - expect(noneof2(1)).toBe(true); - expect(noneof3(1)).toBe(true); - expect(noneof4(1)).toBe(true); - expect(noneof5(1)).toBe(true); - expect(noneof6(1)).toBe(true); - expect(noneof7(1)).toBe(true); - expect(noneof8(1)).toBe(true); - expect(noneof9(1)).toBe(true); - expect(noneof10(1)).toBe(true); - expect(noneof11(1)).toBe(true); - }); - - test(`linearity (or)`, () => { - const or0 = p.or(); - const or1 = p.or(Number.eq(0)); - const or2 = p.or(Number.eq(0), Number.eq(1)); - const or3 = p.or(Number.eq(0), Number.eq(1), Number.eq(2)); - const or4 = p.or(Number.eq(0), Number.eq(1), Number.eq(2), Number.eq(3)); - const or5 = p.or(Number.eq(0), Number.eq(1), Number.eq(2), Number.eq(3), Number.eq(4)); - const or6 = p.or(Number.eq(0), Number.eq(1), Number.eq(2), Number.eq(3), Number.eq(4), Number.eq(5)); - const or7 = p.or(Number.eq(0), Number.eq(1), Number.eq(2), Number.eq(3), Number.eq(4), Number.eq(5), Number.eq(6)); - const or8 = p.or(Number.eq(0), Number.eq(1), Number.eq(2), Number.eq(3), Number.eq(4), Number.eq(5), Number.eq(6), Number.eq(7)); - const or9 = p.or(Number.eq(0), Number.eq(1), Number.eq(2), Number.eq(3), Number.eq(4), Number.eq(5), Number.eq(6), Number.eq(7), Number.eq(8)); - const or10 = p.or(Number.eq(0), Number.eq(1), Number.eq(2), Number.eq(3), Number.eq(4), Number.eq(5), Number.eq(6), Number.eq(7), Number.eq(8), Number.eq(9)); - const or11 = p.or(Number.eq(0), Number.eq(1), Number.eq(2), Number.eq(3), Number.eq(4), Number.eq(5), Number.eq(6), Number.eq(7), Number.eq(8), Number.eq(9), Number.eq(10)); - - expect(or0(0)).toBe(true); - expect(or1(0)).toBe(true); - expect(or2(1)).toBe(true); - expect(or3(2)).toBe(true); - expect(or4(3)).toBe(true); - expect(or5(4)).toBe(true); - expect(or6(5)).toBe(true); - expect(or7(6)).toBe(true); - expect(or8(7)).toBe(true); - expect(or9(8)).toBe(true); - expect(or10(9)).toBe(true); - expect(or11(10)).toBe(true); - expect(or11(11)).toBe(false); - }); -}); \ No newline at end of file diff --git a/src/__tests__/promise.test.ts b/src/__tests__/promise.test.ts deleted file mode 100644 index 022ee0b..0000000 --- a/src/__tests__/promise.test.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { describe, expect, test, vitest } from 'vitest'; -import * as p from '../promise'; - -describe(`promise`, () => { - test(p.map.name, async () => { - const m = p.map((a: string) => a.length); - const ok = Promise.resolve('hello'); - const ko = Promise.reject('hello'); - const ko2 = Promise.reject(new TypeError('err')); - - expect(await m(ok)).toBe(5); - expect(await m(ko)).toBeInstanceOf(Error); - expect(await m(ko2)).toBeInstanceOf(Error); - }); - - test(p.mapOr.name, async () => { - const m = p.mapOr(0, (a: string) => a.length); - const ok = Promise.resolve('hello'); - const ko = Promise.reject('hello'); - - expect(await m(ok)).toBe(5); - expect(await m(ko)).toBe(0); - }); - - test(p.mapOrElse.name, async () => { - const m = p.mapOrElse(() => 0, (a: string) => a.length); - const ok = Promise.resolve('hello'); - const ko = Promise.reject('foobar'); - - expect(await m(ok)).toBe(5); - expect(await m(ko)).toBe(0); - }); - - test(p.all.name, async () => { - expect(await p.all([Promise.resolve('hello')])).toEqual(['hello']); - }); - - test(p.resolve.name, async () => { - expect(await p.resolve('hello')).toEqual(await Promise.resolve('hello')); - }); - - test(p.reject.name, async () => { - let r1; - let r2; - - try { await p.reject('hello'); } catch (e) { r1 = e; } - try { await Promise.reject('hello'); } catch (e) { r2 = e; } - - expect(r1).toEqual(r2); - }); - - test(p.make.name, async () => { - let fn1 = vitest.fn(); - let fn2 = vitest.fn(); - let p1 = p.make((resolve) => resolve('hello')); - let p2 = p.make((_, reject) => reject('hello')); - - await p1.then(fn1); - await p2.catch(fn2); - - expect(fn1).toHaveBeenCalledWith('hello'); - expect(fn2).toHaveBeenCalledWith('hello'); - }); -}); diff --git a/src/__tests__/readme.test.ts b/src/__tests__/readme.test.ts deleted file mode 100644 index 0646e4a..0000000 --- a/src/__tests__/readme.test.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { describe, expect, test } from 'vitest'; -import * as array from '../array'; -import * as n from '../num'; -import * as obj from '../obj'; -import * as o from '../option'; -import * as p from '../predicate'; -import * as str from '../str'; - -describe(`readme examples`, () => { - test(`predicate example`, () => { - const inrange = p.and(n.gt(0), n.lt(10)); - const outofrange = p.invert(inrange); - - expect(inrange(2)).toBe(true); - expect(inrange(0)).toBe(false); - expect(outofrange(0)).toBe(true); - expect(outofrange(2)).toBe(false); - }); - - test(`typeguards`, () => { - type UserArray = User[]; - - interface User { - age: number; - email: string; - firstname: string; - lastname: string; - nickname?: string; - } - - const isUser = obj.guardOf({ - age: n.guard, - email: str.guard, - firstname: str.guard, - lastname: str.guard, - nickname: o.isOptionOf(str.guard), - }); - - const isUserArray = array.guardOf(isUser); - - const user000: User = { - age: 22, - email: 'hello.world@foo.bar', - firstname: 'Hello', - lastname: 'World', - }; - - const user001: User = { - age: 54, - email: 'john.doe@domain.com', - firstname: 'John', - lastname: 'Doe', - nickname: 'BionicPizza', - }; - - isUser(user000); // true, user000 is User - isUser(user001); // true, user001 is User - isUser(1000000); // false - isUserArray([user000, user001]); // true - isUserArray(['pizza', user001]); // false - }); -}); \ No newline at end of file diff --git a/src/__tests__/result.test.ts b/src/__tests__/result.test.ts deleted file mode 100644 index a41a34a..0000000 --- a/src/__tests__/result.test.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { describe, expect, test } from 'vitest'; -import * as r from '../result'; - -describe(`result`, () => { - test(`checks if is ok or err`, () => { - expect(r.isOk(10)).toBe(true); - expect(r.isOk(Error)).toBe(true); - expect(r.isOk(new Error())).toBe(false); - expect(r.isErr(new Error())).toBe(true); - expect(r.isErr(Error)).toBe(false); - expect(r.isErr(null)).toBe(false); - expect(r.isErr({ name: 'foo', message: 'bar', cause: 'baz' })).toBe(true); - }); - - test('err', () => { - const a: any = {}; - const b: any = {}; - - a.b = b; - a.a = a; - b.a = a; - b.b = b; - - expect(r.err('foo')).toBeInstanceOf(Error); - expect(r.err({ message: 'foo' })).toBeInstanceOf(Error); - expect(r.err({ data: 'foo' })).toBeInstanceOf(Error); - expect(r.err(20)).toBeInstanceOf(Error); - // expect(r.err(20n)).toBeInstanceOf(Error); - expect(r.err([10, 20, 30])).toBeInstanceOf(Error); - expect(r.err(false)).toBeInstanceOf(Error); - expect(r.err(true)).toBeInstanceOf(Error); - expect(r.err(Infinity)).toBeInstanceOf(Error); - expect(r.err(new Array())).toBeInstanceOf(Error); - expect(r.err({})).toBeInstanceOf(Error); - expect(r.err(new Error('foo'))).toBeInstanceOf(Error); - expect(r.err(new Error(a))).toBeInstanceOf(Error); - expect(r.err(new Error(b))).toBeInstanceOf(Error); - }); - - test(`isResultOf`, () => { - const g = (x: unknown): x is number => typeof x === 'number'; - const gof = r.isResultOf(g); - - expect(gof(10)).toBe(true); - expect(gof(Error)).toBe(false); - expect(gof(new Error())).toBe(true); - expect(gof('hello')).toBe(false); - }); - - test(`comparables`, () => { - expect(r.cmp(10, 20)).toBe(-1); - expect(r.cmp(20, 10)).toBe(1); - expect(r.cmp(10, 10)).toBe(0); - expect(r.cmp(10, new Error())).toBe(0); - expect(r.cmp(new Error(), 10)).toBe(0); - expect(r.cmp(new Error(), new TypeError())).toBe(0); - - }); - - test(`equatables`, () => { - expect(r.eq(20, 20)).toBe(true); - expect(r.eq(new Error(), new TypeError())).toBe(true); - expect(r.eq(new Error(), new Error())).toBe(true); - expect(r.eq(10, 20)).toBe(false); - }); - - test(`filter`, () => { - const filterfn = (arg: number) => arg > 0; - const filter = r.filter(filterfn); - const e = new TypeError('hello'); - - expect(filter(10)).toBe(10); - expect(filter(0)).toEqual(new Error(`0 is not ok`)); - expect(filter(e)).toEqual(e); - }); - - test(`mappables`, () => { - const mapfn = (s: string) => s.length; - const map = r.map(mapfn); - const mapor = r.mapOr(0, mapfn); - const maporelse = r.mapOrElse(() => 0, mapfn); - - const ok = 'hello'; - const err = new Error(); - - expect(map(ok)).toBe(5); - expect(map(err)).toBe(err); - expect(mapor(ok)).toBe(5); - expect(mapor(err)).toBe(0); - expect(maporelse(ok)).toBe(5); - expect(maporelse(err)).toBe(0); - }); - - test(`unwrappables`, () => { - const ok = 'hello' as r.result; - const err = new Error(); - const or = `world` as r.result; - const orElse = () => or; - - expect(r.unwrap(ok)).toBe(ok); - expect(() => r.unwrap(err)).toThrow(); - expect(r.unwrapOr(or)(ok)).toBe(ok); - expect(r.unwrapOr(or)(err)).toBe(or); - expect(r.unwrapOrElse(orElse)(ok)).toBe(ok); - expect(r.unwrapOrElse(orElse)(err)).toBe(or); - }); -}); \ No newline at end of file diff --git a/src/__tests__/set.test.ts b/src/__tests__/set.test.ts deleted file mode 100644 index 3d3cdb3..0000000 --- a/src/__tests__/set.test.ts +++ /dev/null @@ -1,158 +0,0 @@ -import { describe, expect, test, vitest } from 'vitest'; -import { Number, String } from '..'; -import * as Set from '../set'; - -describe(`set`, () => { - test(Set.add.name, () => { - const s = Set.make(1, 2, 3); - const add = Set.add(s); - const has4 = Set.has(4); - - expect(has4(s)).toBeFalsy(); - - add(4); - - expect(has4(s)).toBeTruthy(); - }); - test(Set.clear.name, () => { - const s = Set.make(1, 2, 3); - - expect(Set.size(s)).toBe(3); - Set.clear(s); - expect(Set.size(s)).toBe(0); - }); - test(Set.concat.name, () => { - const s1 = Set.make(1, 2, 3); - const s2 = Set.make(4, 5, 6); - const t = new globalThis.Set([1, 2, 3, 4, 5, 6]); - - expect(Set.concat(s1)(s2)).toEqual(t); - }); - test(Set.delete.name, () => { - const s = Set.make(1, 2, 3); - - expect(Set.has(1)(s)).toBeTruthy(); - Set.delete(1)(s); - expect(Set.has(1)(s)).toBeFalsy(); - }); - test(Set.entries.name, () => { - const s = Set.make(1, 2, 3); - - expect(Set.entries(s)).toEqual(new globalThis.Set([1, 2, 3]).entries()); - }); - test(Set.every.name, () => { - const s1 = Set.make(1, 2, 3); - const s2 = Set.make(1, 2, 0); - const p = Set.every(Number.gt(0)); - - expect(p(s1)).toBe(true); - expect(p(s2)).toBe(false); - }); - test(Set.filter.name, () => { - const s1 = Set.make(1, 2, 3); - - expect(Set.filter(Number.gt(0))(s1)).toEqual(new globalThis.Set([1, 2, 3])); - expect(Set.filter(Number.lt(2))(s1)).toEqual(new globalThis.Set([1])); - }); - test(Set.forEach.name, () => { - const s = Set.make(1, 2, 3); - const fn = vitest.fn(); - const foreach = Set.forEach(fn); - - foreach(s); - - expect(fn).toHaveBeenCalledTimes(3); - expect(fn).toHaveBeenCalledWith(1, 1, s); - expect(fn).toHaveBeenCalledWith(2, 2, s); - expect(fn).toHaveBeenCalledWith(3, 3, s); - }); - test(Set.guard.name, () => { - expect(Set.guard(Set.make(1, 2, 3))).toBe(true); - expect(Set.guard(new globalThis.Set())).toBe(true); - expect(Set.guard(10)).toBe(false); - }); - test(Set.guardOf.name, () => { - const s1 = Set.make(1, 2, 3); - const s2 = Set.make('pineapple', 'on pizza', 'is a huge mistake'); - const numberSet = Set.guardOf(Number.guard); - const stringSet = Set.guardOf(String.guard); - - expect(numberSet(s1)).toBe(true); - expect(numberSet(s2)).toBe(false); - expect(stringSet(s2)).toBe(true); - expect(stringSet(s1)).toBe(false); - }); - test(Set.has.name, () => { - const s1 = Set.make(1, 2, 3); - const has1 = Set.has(1); - const has4 = Set.has(4); - - expect(has1(s1)).toBeTruthy(); - expect(has4(s1)).toBeFalsy(); - }); - test(Set.intersect.name, () => { - const s1 = Set.make(1, 2, 3); - const s2 = Set.make(2, 3, 4); - - expect(Set.intersect(s1, s2)).toEqual(new globalThis.Set([2, 3])); - }); - test(Set.make.name, () => { - expect(Set.make(null)).toEqual(new globalThis.Set([])); - expect(Set.make(1, 2, 3)).toEqual(new globalThis.Set([1, 2, 3])); - expect(Set.make(1)).toEqual(new globalThis.Set([1])); - expect(Set.make([1, 2, 3, 4])).toEqual(new globalThis.Set([1, 2, 3, 4])); - expect(Set.make([1, 2, 3, 4], 5)).toEqual(new globalThis.Set([1, 2, 3, 4, 5])); - }); - test(Set.map.name, () => { - const s1 = Set.make(1, 2, 3); - const map = Set.map(Number.uadd(1)); - - expect(map(s1)).toEqual(new globalThis.Set([2, 3, 4])); - }); - test(Set.none.name, () => { - const s1 = Set.make(1, 2, 3); - - expect(Set.none(Number.gt(0))(s1)).toBe(false); - expect(Set.none(Number.lt(2))(s1)).toBe(false); - expect(Set.none(Number.gt(4))(s1)).toBe(true); - }); - test(Set.reduce.name, () => { - const s1 = Set.make(1, 2, 3); - - expect(Set.reduce(Number.badd, 0)(s1)); - }); - test(Set.reduceRight.name, () => { - const s1 = Set.make(3, 2, 1); - - expect(Set.reduceRight(Number.bsub, 5)(s1)).toBe(-1); - }); - test(Set.size.name, () => { - const s1 = Set.make(1, 2, 3); - - expect(Set.size(s1)).toBe(3); - }); - test(Set.some.name, () => { - const s1 = Set.make(1, 2, 3); - - expect(Set.some(Number.gt(0))(s1)).toBe(true); - expect(Set.some(Number.lt(2))(s1)).toBe(true); - expect(Set.some(Number.gt(4))(s1)).toBe(false); - }); - test(Set.toArray.name, () => { - const s1 = Set.make(1, 2, 3); - - expect(Set.toArray(s1)).toEqual([1, 2, 3]); - }); - test(Set.union.name, () => { - const a = Set.make(1, 2, 3); - const b = Set.make(3, 4, 5); - - expect(Set.union(a, b)).toEqual(new globalThis.Set([1, 2, 3, 4, 5])); - }); - test(Set.values.name, () => { - const s = Set.make(1, 2, 3); - const v = Set.values(s); - - expect(v).toEqual(new globalThis.Set([1, 2, 3]).values()); - }); -}); diff --git a/src/__tests__/str.test.ts b/src/__tests__/str.test.ts deleted file mode 100644 index 9b7ade6..0000000 --- a/src/__tests__/str.test.ts +++ /dev/null @@ -1,264 +0,0 @@ -import { describe, expect, test } from 'vitest'; -import * as s from '../str'; - -describe(`str`, () => { - test(s.asc.name, () => { - const array = ["c", "b", "a"]; - - expect(expect.arrayContaining(array.sort(s.asc))).toEqual(["a", "b", "c"]); - }); - - test(s.bconcat.name, () => { - expect(s.bconcat('a', 'b')).toBe('ab'); - }); - - test(s.includes.name, () => { - expect(s.includes('hello', 'll')).toBe(true); - expect(s.includes('hello', 'x')).toBe(false); - }); - - test(s.indexOf.name, () => { - expect(s.indexOf('hello', 'l')).toBe(2); - expect(s.indexOf('hello', 'x')).toBe(-1); - }); - - test(s.blastIndexOf.name, () => { - expect(s.blastIndexOf('hello', 'l')).toBe(3); - expect(s.blastIndexOf('hello', 'x')).toBe(-1); - }); - - test(s.match.name, () => { - expect(expect.arrayContaining(s.match('hello', /l/)!)).toEqual(["l"]); - expect(s.match('hello', /x/)).toBe(null); - }); - - test(s.padEnd.name, () => { - expect(s.padEnd('1', 5)).toBe('1 '); - expect(s.padEnd('1', 5, 'x')).toBe('1xxxx'); - }); - - test(s.padstart.name, () => { - expect(s.padstart('1', 5)).toBe(' 1'); - expect(s.padstart('1', 5, 'x')).toBe('xxxx1'); - }); - - test(s.brepeat.name, () => { - expect(s.brepeat('1', 5)).toBe('11111'); - }); - - test(s.replace.name, () => { - expect(s.replace('hello', 'l', 'x')).toBe('hexlo'); - expect(s.replace('hello', 'x', 'l')).toBe('hello'); - }); - - test(s.search.name, () => { - expect(s.search('hello', 'll')).toBe(2); - }); - - test(s.slice.name, () => { - expect(s.slice('hello')).toBe('hello'); - expect(s.slice('hello', 2)).toBe('llo'); - expect(s.slice('hello', 1, 3)).toBe('el'); - }); - - test(s.split.name, () => { - expect(s.split('hello', 'l')).toEqual(['he', '', 'o']); - }); - - test(s.camel.name, () => { - expect(s.camel('hello world')).toBe('helloWorld'); - }); - - test(s.ucharAt.name, () => { - expect(s.ucharAt(-1)('hello')).toBe(''); - expect(s.ucharAt(200)('hello')).toBe(''); - expect(s.ucharAt(0)('hello')).toBe('h'); - expect(s.ucharAt(1)('hello')).toBe('e'); - expect(s.ucharAt(2)('hello')).toBe('l'); - expect(s.ucharAt(3)('hello')).toBe('l'); - expect(s.ucharAt(4)('hello')).toBe('o'); - expect(s.ucharAt(5)('hello')).toBe(''); - }); - - test(s.cmp.name, () => { - expect(s.cmp('a', 'b')).toBe(-1); - expect(s.cmp('a', 'a')).toBe(0); - expect(s.cmp('b', 'a')).toBe(1); - }); - - test(s.desc.name, () => { - const array = ['a', 'b', 'c']; - - expect(expect.arrayContaining(array.sort(s.desc))).toEqual(['c', 'b', 'a']); - }); - - test(s.empty.name, () => { - expect(s.empty('')).toBe(true); - expect(s.empty(' ')).toBe(false); - }); - - test(s.uendsWith.name, () => { - expect(s.uendsWith('lo')('hello')).toBe(true); - expect(s.uendsWith('x')('hello')).toBe(false); - }); - - test(s.eq.name, () => { - expect(s.eq('a', 'a')).toBe(true); - expect(s.eq('a', 'b')).toBe(false); - }); - - test(s.fromCharCode.name, () => { - expect(s.fromCharCode(97)).toBe('a'); - expect(s.fromCharCode(98)).toBe('b'); - }); - - test(s.guard.name, () => { - expect(s.guard('hello')).toBe(true); - expect(s.guard(null)).toBe(false); - }); - - test(s.kebab.name, () => { - expect(s.kebab('helloWorld')).toBe('hello-world'); - }); - - test(s.lower.name, () => { - expect(s.lower('HELLO')).toBe('hello'); - }); - - test(s.pascal.name, () => { - expect(s.pascal('helloWorld')).toBe('HelloWorld'); - expect(s.pascal('hello world')).toBe('HelloWorld'); - }); - - test(s.snake.name, () => { - expect(s.snake('helloWorld')).toBe('hello_world'); - }); - - test(s.trim.name, () => { - expect(s.trim(' hello ')).toBe('hello'); - }); - - test(s.trimEnd.name, () => { - expect(s.trimEnd(' hello ')).toBe(' hello'); - }); - - test(s.trimStart.name, () => { - expect(s.trimStart(' hello ')).toBe('hello '); - }); - - test(s.uconcat.name, () => { - expect(s.uconcat('world')('hello')).toBe('helloworld'); - }); - - test(s.uincludes.name, () => { - expect(s.uincludes('ll')('hello')).toBe(true); - expect(s.uincludes('x')('hello')).toBe(false); - }); - - test(s.uindexOf.name, () => { - expect(s.uindexOf('l')('hello')).toBe(2); - expect(s.uindexOf('x')('hello')).toBe(-1); - }); - - test(s.ulastIndexOf.name, () => { - expect(s.ulastIndexOf('l')('hello')).toBe(3); - expect(s.ulastIndexOf('x')('hello')).toBe(-1); - }); - - test(s.umatch.name, () => { - expect(expect.arrayContaining(s.umatch(/l/)('hello')!)).toEqual(["l"]); - expect(s.umatch(/x/)('hello')).toBe(null); - }); - - test(s.upadEnd.name, () => { - expect(s.upadEnd(5)('1')).toBe('1 '); - expect(s.upadEnd(5, 'x')('1')).toBe('1xxxx'); - }); - - test(s.upadstart.name, () => { - expect(s.upadstart(5)('1')).toBe(' 1'); - expect(s.upadstart(5, 'x')('1')).toBe('xxxx1'); - }); - - test(s.upper.name, () => { - expect(s.upper('hello')).toBe('HELLO'); - }); - - test(s.urepeat.name, () => { - expect(s.urepeat(5)('1')).toBe('11111'); - }); - - test(s.ureplace.name, () => { - expect(s.ureplace('l', 'x')('hello')).toBe('hexlo'); - }); - - test(s.usearch.name, () => { - expect(s.usearch('ll')('hello')).toBe(2); - expect(s.usearch('x')('hello')).toBe(-1); - }); - - test(s.uslice.name, () => { - expect(s.uslice()('hello')).toBe('hello'); - expect(s.uslice(1)('hello')).toBe('ello'); - expect(s.uslice(undefined, 4)('hello')).toBe('hell'); - expect(s.uslice(1, 3)('hello')).toBe('el'); - }); - - test(s.usplit.name, () => { - expect(s.usplit('l')('hello')).toEqual(['he', '', 'o']); - }); - - test(s.ucharCodeAt.name, () => { - expect(s.ucharCodeAt(0)('hello')).toBe(104); - expect(s.ucharCodeAt(1)('hello')).toBe(101); - expect(s.ucharCodeAt(2)('hello')).toBe(108); - expect(s.ucharCodeAt(3)('hello')).toBe(108); - expect(s.ucharCodeAt(4)('hello')).toBe(111); - expect(s.ucharCodeAt(5)('hello')).toBe(null); - }); - - test(s.ucharAt.name, () => { - expect(s.ucharAt(0)('hello')).toBe('h'); - expect(s.ucharAt(1)('hello')).toBe('e'); - expect(s.ucharAt(2)('hello')).toBe('l'); - expect(s.ucharAt(3)('hello')).toBe('l'); - expect(s.ucharAt(4)('hello')).toBe('o'); - expect(s.ucharAt(5)('hello')).toBe(``); - }); - - test(s.length.name, () => { - expect(s.length('hello')).toBe(5); - }); - - test(s.words.name, () => { - expect(s.words('hello world')).toEqual(['hello', 'world']); - }); - - test(s.lines.name, () => { - expect(s.lines('hello\nworld')).toEqual(['hello', 'world']); - }); - - test(s.chars.name, () => { - expect(s.chars('hello')).toEqual(['h', 'e', 'l', 'l', 'o']); - }); - - test(s.reverse.name, () => { - expect(s.reverse('hello')).toEqual('olleh'); - }); - - test(s.prefix.name, () => { - expect(s.prefix('world', 'hello')).toEqual('helloworld'); - }); - - test(s.uprefix.name, () => { - expect(s.uprefix('world')('hello')).toEqual('worldhello'); - }); - - test(s.suffix.name, () => { - expect(s.suffix('world', 'hello')).toEqual('worldhello'); - }); - - test(s.usuffix.name, () => { - expect(s.usuffix('world')('hello')).toEqual('helloworld'); - }); -}); \ No newline at end of file diff --git a/src/__tests__/try.test.ts b/src/__tests__/try.test.ts deleted file mode 100644 index 9932405..0000000 --- a/src/__tests__/try.test.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { describe, expect, test } from 'vitest'; -import * as m from '../maybe'; -import * as num from '../num'; -import * as o from '../option'; -import * as r from '../result'; -import * as tryto from '../try'; - -describe(`try`, () => { - const fn = (arg: number) => { - if (num.isEven(arg)) { - return arg * 2; - } - - throw new Error(`${arg} is not even`); - }; - - const fnasync = async (arg: number) => { - if (num.isEven(arg)) { - return Promise.resolve(arg * 2); - } - - return Promise.reject(new Error(`${arg} is not even`)); - }; - - test(tryto.maybe.name, () => { - expect(tryto.maybe(fn)(2)).toEqual(4); - expect(tryto.maybe(fn)(3)).toEqual(null); - expect(m.isJust(tryto.maybe(fn)(2))).toBeTruthy(); - expect(m.isNothing(tryto.maybe(fn)(3))).toBeTruthy(); - }); - - test(tryto.maybeAsync.name, async () => { - expect(await tryto.maybeAsync(fnasync)(2)).toEqual(4); - expect(await tryto.maybeAsync(fnasync)(3)).toEqual(null); - expect(m.isJust(await tryto.maybeAsync(fnasync)(2))).toBeTruthy(); - expect(m.isNothing(await tryto.maybeAsync(fnasync)(3))).toBeTruthy(); - }); - - test(tryto.option.name, () => { - expect(tryto.option(fn)(2)).toEqual(4); - expect(tryto.option(fn)(3)).toEqual(null); - expect(o.isSome(tryto.option(fn)(2))).toBeTruthy(); - expect(o.isNone(tryto.option(fn)(3))).toBeTruthy(); - }); - - test(tryto.optionAsync.name, async () => { - expect(await tryto.optionAsync(fnasync)(2)).toEqual(4); - expect(await tryto.optionAsync(fnasync)(3)).toEqual(null); - expect(o.isSome(await tryto.optionAsync(fnasync)(2))).toBeTruthy(); - expect(o.isNone(await tryto.optionAsync(fnasync)(3))).toBeTruthy(); - }); - - test(tryto.result.name, () => { - expect(tryto.result(fn)(2)).toEqual(4); - expect(tryto.result(fn)(3)).toEqual(new Error(`3 is not even`)); - expect(r.isOk(tryto.result(fn)(2))).toBeTruthy(); - expect(r.isErr(tryto.result(fn)(3))).toBeTruthy(); - }); - - test(tryto.resultAsync.name, async () => { - expect(await tryto.resultAsync(fnasync)(2)).toEqual(4); - expect(await tryto.resultAsync(fnasync)(3)).toEqual(new Error(`3 is not even`)); - expect(r.isOk(await tryto.resultAsync(fnasync)(2))).toBeTruthy(); - expect(r.isErr(await tryto.resultAsync(fnasync)(3))).toBeTruthy(); - }); -}); diff --git a/src/__tests__/tuple.test.ts b/src/__tests__/tuple.test.ts deleted file mode 100644 index 98a7f1c..0000000 --- a/src/__tests__/tuple.test.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { describe, expect, test } from 'vitest'; -import * as num from '../num'; -import * as o from '../option'; -import * as str from '../str'; -import * as t from '../tuple'; - -describe(`tuple`, () => { - test(`guardOf`, () => { - const guard = t.guardOf(num.guard, str.guard, num.guard); - - expect(guard([1, 'a', 2])).toBe(true); - expect(guard([1, 'a', 'b'])).toBe(false); - }); - - test(`cmp`, () => { - const a: t.tuple<[number, number, number]> = [1, 2, 3]; - const b: t.tuple<[number, number, number]> = [1, 2, 3]; - const c: t.tuple<[number, number, number]> = [1, 2, 4]; - const d: t.tuple<[number, number]> = [1, 2]; - - expect(t.cmp(a, b)).toBe(0); - expect(t.cmp(a, c)).toBe(-1); - expect(t.cmp(c, a)).toBe(1); - expect(t.cmp(d, a)).toBe(-1); - expect(t.cmp(a, d)).toBe(1); - expect(t.cmp(null, a)).toBe(-1); - expect(t.cmp(null, undefined)).toBe(0); - }); - - test(`eq`, () => { - const a: t.tuple<[number, number, number]> = [1, 2, 3]; - const b: t.tuple<[number, number, number]> = [1, 2, 3]; - const c: t.tuple<[number, number, number]> = [1, 2, 4]; - const d: t.tuple<[number, number]> = [1, 2]; - - expect(t.eq(a, b)).toBe(true); - expect(t.eq(a, c)).toBe(false); - expect(t.eq(c, a)).toBe(false); - expect(t.eq(d, a as any)).toBe(false); - expect(t.eq(a, d as any)).toBe(false); - expect(t.eq(null, a)).toBe(false); - expect(t.eq(null, undefined)).toBe(true); - }); - - test(`get`, () => { - const tuple: t.tuple<[number, number, number]> = [10, 20, 30]; - - expect(t.get(0)(tuple)).toBe(10); - expect(t.get(5)(tuple)).toBe(null); - - expect(o.isSome(t.get(0)(tuple))).toBe(true); - expect(o.isNone(t.get(5)(tuple))).toBe(true); - }); - - test(`intersect`, () => { - const a: t.tuple<[number, number, number]> = [1, 2, 3]; - const b: t.tuple<[number, number, number]> = [1, 2, 3]; - const c: t.tuple<[number, number, number]> = [1, 2, 4]; - const d: t.tuple<[number, number]> = [1, 2]; - - expect(t.intersect(a)(b)).toEqual(a); - expect(t.intersect(a)(c)).toEqual([1, 2]); - expect(t.intersect(a)(d)).toEqual([1, 2]); - }); - - test(`length`, () => { - const tuple: t.tuple<[number, number, number]> = [10, 20, 30]; - - expect(t.length(tuple)).toBe(3); - expect(t.length([])).toBe(0); - }); - - test(`map`, () => { - const tuple: t.tuple<[number, number, number]> = [10, 20, 4]; - const m = t.map(num.uadd(2), num.udiv(2), num.uroot(2)); - - expect(m(tuple)).toEqual([12, 10, 2]); - }); - - test(`union`, () => { - const a: t.tuple<[number, number, number]> = [1, 2, 3]; - const b: t.tuple<[number, number, number]> = [1, 2, 3]; - const c: t.tuple<[number, number, number]> = [1, 2, 4]; - const d: t.tuple<[number, number]> = [1, 2]; - - expect(t.union(a)(b)).toEqual(a); - expect(t.union(a)(c)).toEqual([1, 2, 3, 4]); - expect(t.union(a)(d)).toEqual([1, 2, 3]); - }); -}); diff --git a/src/array.ts b/src/array.ts deleted file mode 100644 index 49bf1c1..0000000 --- a/src/array.ts +++ /dev/null @@ -1,666 +0,0 @@ -import type * as f from './functors'; -import type { option } from './option'; -import type { result } from './result'; -import type * as fn from './fn'; - -/** - * Compares two arrays. - * - * ```typescript - * import { Array } from 'tiinvo'; - * - * console.log(Array.cmp(['a'], ['a'])) // 0 - * console.log(Array.cmp(['a'], ['b'])) // -1 - * console.log(Array.cmp(['b'], ['a'])) // 1 - * console.log(Array.cmp(['a'], ['a', 'b'])) // -1 - * console.log(Array.cmp(['a', 'b'], ['a'])) // 1 - * console.log(Array.cmp(['a', 'b'], ['a', 'b'])) // 0 - * console.log(Array.cmp(['a', 'b', 'c'], ['a', 'b'])) // 1 - * console.log(Array.cmp(['a', 'b', 'c'], ['a', 'b', 'c'])) // 0 - * ``` - * - * @param a - * @param b - * @returns - * @since 3.0.0 - */ -export const cmp: f.comparableE = (a: a, b: b): -1 | 0 | 1 => ((a === null || a === undefined) && (b === null || b === undefined)) ? 0 : a as any > b as any ? 1 : a as any < b as any ? -1 : 0 -/** - * Returns true if two arrays are identical. - * - * ```typescript - * import { Array } from 'tiinvo'; - * - * console.log(Array.eq(['a'], ['a'])) // true - * console.log(Array.eq(['a'], ['b'])) // false - * console.log(Array.eq(['b'], ['a'])) // false - * console.log(Array.eq(['a'], ['a', 'b'])) // false - * console.log(Array.eq(['a', 'b'], ['a'])) // false - * console.log(Array.eq(['a', 'b'], ['a', 'b'])) // true - * ``` - * - * @param a - * @param b - * @returns - * @since 3.0.0 - */ -export const eq: f.equatableE = (a, b) => a.length === b.length && cmp(a, b) === 0; -/** - * Returns true if the array `a` is empty. - * - * ```typescript - * import { Array } from 'tiinvo'; - * - * console.log(Array.empty([])) // true - * console.log(Array.empty(['a'])) // false - * ``` - * - * @param a - * @returns - * @since 3.0.0 - */ -export const empty: f.predicateE = a => a.length === 0; -/** - * Returns true if `a` is an array. - * - * ```typescript - * import { Array } from 'tiinvo'; - * - * console.log(Array.guard([])) // true - * console.log(Array.guard(null)) // false - * console.log(Array.guard(undefined)) // false - * console.log(Array.guard(0)) // false - * console.log(Array.guard('')) // false - * ``` - * - * @since 3.0.0 - */ -export const guard = (a => Array.isArray(a)) as f.guard -/** - * Returns true if `b` is an array of `a`. - * - * ```typescript - * import { Array, Str } from 'tiinvo'; - * - * const isstrarr = Array.guardOf(Str.guard); - * - * console.log(isstrarr([])) // true - * console.log(isstrarr(['a'])) // true - * console.log(isstrarr(['a', 'b'])) // true - * console.log(isstrarr(['a', 'b', 'c'])) // true - * console.log(isstrarr(['a', 'b', 'c', 1])) // false - * ```` - * - * @param a - * @returns - * @since 3.0.0 - */ -export const guardOf = (a: f.guard) => (b => Array.isArray(b) && b.every(a)) as f.guard -/** - * Concates `b` and `a` without modifying the original arrays. - * - * ```typescript - * import { Array } from 'tiinvo'; - * - * console.log(Array.concat(['a'])(['b'])) // ['b', 'a'] - * ``` - * @param a - * @returns - * @since 3.0.0 - */ -export const concat = (a: a[]) => (b: a[]) => b.concat(a); -/** - * Returns true if an array `b` contains `a`. - * - * ```typescript - * import { Array } 'tiinvo'; - * - * console.log(Array.contains('a')(['a'])) // true - * console.log(Array.contains('a')(['b'])) // false - * ``` - * - * @param a - * @returns - * @since 3.0.0 - */ -export const contains = (a: a) => (b: a[]) => b.indexOf(a) >= 0; -/** - * Determines whether all the members of an array `a` satisfy the specified predicate `p`. - * - * ```typescript - * import { Array, Num } 'tiinvo'; - * - * const everyeven = Array.every(Num.iseven); - * - * console.log(everyeven([2, 4, 6])) // true - * console.log(everyeven([2, 4, 5])) // false - * ``` - * - * @param p - * @returns - * @since 3.0.0 - */ -export const every = (p: f.predicateE) => (a: a[]) => a.every(p); -/** - * Creates an array from an array-like object. - * - * ```typescript - * import { Array } 'tiinvo'; - * - * console.log(Array.from([1, 2, 3])) // [1, 2, 3] - * console.log(Array.from(new Set([1, 2, 3]))) // [1, 2, 3] - * ``` - * - * @param a - * @returns - * @since 3.0.8 - */ -export const from = Array.from; -/** - * Returns the element `result` at index `i` of an array `a[]`. - * - * ```ts - * import { Array } 'tiinvo'; - * - * console.log(Array.get(1)(['a', 'b', 'c'])) // 'b' - * console.log(Array.get(2)(['a'])) // Error('Index 2 is out of bounds for length 1') - * ``` - * - * @param i - * @returns - * @since 3.0.0 - */ -export const get = (i: number) => (a: a[]): result => { - if (i > 0 && i < a.length) { - return a[i]; - } - - return new Error(`Index out of bounds ${i} for length ${a.length}`); -}; - -/** - * Fills an array `b` with `a` from index `start` to `end`. - * This do not modify the original array. - * - * ```ts - * import { Array } from 'tiinvo'; - * - * const f = Array.fill(0, 1, 2); - * - * console.log(f([10, 20, 30, 40])) // [10, 0, 0, 40] - * ``` - * - * @param a - * @param start - * @param end - * @returns - * @since 3.3.0 - */ -export const fill = (a: a, start?: number, end?: number) => (b: a[]) => { - const s = start || 0; - const e = end || b.length - 1; - const c = Array.from(b); - - for (let i = 0; i < c.length; i++) { - if (i >= s && i <= e) { - c[i] = a; - } else { - c[i] = b[i]; - } - } - - return c; -} -/** - * Returns the elements of an array `a` that meet the condition specified in a predicate `p`. - * - * ```typescript - * import { Array, Predicate } 'tiinvo'; - * - * console.log(Array.filter(Predicate.eq('a'))(['a', 'b'])) // ['a'] - * console.log(Array.filter(Predicate.eq('b'))(['a', 'b'])) // ['b'] - * ``` - * - * @param p - * @returns - * @since 3.0.0 - */ -export const filter = (p: f.predicateE) => (a: a[]) => a.filter(p); -/** - * Returns the value of the first `option` in the array `a` where predicate `p` is true. - * @param p - * @returns - * @since 3.0.0 - * - * ```ts - * import { Array, Num } 'tiinvo'; - * - * const p = Num.gt(1); - * - * console.log(Array.find(p)([1, 2, 3])) // 2 - * ``` - */ -export const find = (p: f.predicateE) => (a: a[]) => a.find(p) as option; -/** - * Returns the first element of an array `a`. If the array is empty, returns `none`. - * - * ```typescript - * import { Array } 'tiinvo'; - * - * console.log(Array.first(['a', 'b'])) // 'a'; - * console.log(Array.first([])) // null; - * ``` - * - * @param a - * @returns - * @since 3.0.0 - */ -export const first = (a: a[]) => a[0] as option -/** - * Returns the first element of an array `b` or `a` if the array is empty. - * - * ```typescript - * import { Array } 'tiinvo'; - * - * const firstor = a.firstOr(`not found`); - * - * console.log(firstor(['a', 'b'])) // 'a' - * console.log(firstor([])) // `not found` - * ``` - * @param a - * @returns - * @since 3.0.0 - */ -export const firstOr = (a: a) => (b: a[]) => b[0] ?? a; -/** - * Maps an array `a` by removing all elements that do not satisfy the predicate `p`. - * The filter occurs before mapping the elements. - * - * ```typescript - * import { Array, Number } 'tiinvo'; - * - * const fm = Array.filtermap(Number.umul(2), Number.gt(3)); - * - * fm([1, 2, 3, 4, 5]) // [6, 8, 10] - * ``` - * - * @param f - * @param p - * @returns - * @since 3.2.0 - */ -export const filtermap = (f: f.map, p: f.predicateE) => (a: a[]) => { - const r = [] as b[]; - - for (let i = 0; i < a.length; i++) { - const v = a[i]; - - if (p(v)) { - r.push(f(v)); - } - } - - return r; -} -/** - * Flatterns an array - * - * ```typescript - * import { Array } 'tiinvo'; - * - * console.log(Array.flat(['a', ['b', 'c']])) // ['a', 'b', 'c'] - * console.log(Array.flat(['a', ['b', 'c'], 'd'])) // ['a', 'b', 'c', 'd'] - * ``` - * @param a - * @returns - * @since 3.0.0 - */ -export const flat = (a: a[][]) => a.reduce((ac, n) => [...ac, ...n], []); -/** - * Maps a matrix `a[][]` to a `b[]` using the mapping function `f`. - * - * ```typescript - * import { Array, Str } from 'tiinvo'; - * - * const map = Array.flatmap(Str.length); - * - * console.log(map([['abc'], ['cdef']])) // [3, 4] - * ``` - * - * @param f - * @returns - * - * @since 3.0.10 - */ -export const flatmap = (f: f.map) => (a: a[][]): b[] => flat(a.map(e => e.map(f))); -/** - * Calls all functions in the array `a` with the related index of arguments `b`. - * @param a - * @returns - * @since 3.0.0 - * - * ```ts - * import { Array, Num, Str } 'tiinvo'; - * - * console.log(Array.fromfunctions([Num.add(1), Str.upper])([1, 'a'])) // ['2', 'A'] - * ``` - */ -export const fromfunctions = any)[]>(... a: a) => (b: fn.argsOfMany) => a.map((c, i) => c((b)[i])) as unknown as fn.returnTypeOfMany; -/** - * Determines whether an array includes a certain element, returning true or false as appropriate. - * - * ```typescript - * import { Array } 'tiinvo'; - * - * console.log(Array.includes('a')(['a', 'b'])) // true - * console.log(Array.includes('c')(['a', 'b'])) // false - * ``` - * - * @param a - * @returns - * - * @since 3.2.0 - */ -export const includes = (a: a) => (b: a[]) => b.includes(a); -/** - * Returns the last element of an array `a`. If the array is empty, returns `none`. - * - * ```typescript - * import { Array } 'tiinvo'; - * - * console.log(Array.last(['a', 'b'])) // 'b'; - * console.log(Array.last([])) // null; - * ``` - * - * @param a - * @returns - * @since 3.0.0 - */ -export const last = (a: a[]) => a[a.length - 1] as option; -/** - * Returns the last element of an array `b` or `a` if the array is empty. - * - * ```typescript - * import { Array } 'tiinvo'; - * - * const lastor = Array.lastOr(`not found`); - * - * console.log(lastor(['a', 'b'])) // 'b' - * console.log(lastor([])) // `not found` - * ``` - * - * @param a - * @returns - * @since 3.0.0 - */ -export const lastOr = (a: a) => (b: a[]) => b[b.length - 1] ?? a; -/** - * Gets the length of the array. This is a number one higher than the highest index in the array. - * - * ```typescript - * import { Array } 'tiinvo'; - * - * console.log(Array.length(['a', 'b'])) // 2 - * console.log(Array.length([])) // 0 - * ``` - * - * @param a - * @returns - * - * @since 3.0.8 - */ -export const length = (a: a[]) => a.length; -/** - * Adds all the elements of an array into a string, separated by the specified separator string. - * - * By default the `separator` is an empty string. - * - * ```typescript - * import { Array } 'tiinvo'; - * - * console.log(Array.join()(['a', 'b', 'c'])) // 'abc' - * console.log(Array.join(' ')(['a', 'b', 'c'])) // 'a b c' - * console.log(Array.join('-')(['a', 'b', 'c'])) // 'a-b-c' - * ``` - * - * @param separator - * @returns - * @since 3.0.0 - */ -export const join = (separator: string = "") => (a: a[]) => a.join(separator); - -/** - * Creates a new array of a given size - * - * ```ts - * import { Array } from 'tiinvo'; - * - * Array.make(4, 'hello') // ['hello', 'hello', 'hello', 'hello'] - * Array.make(2) // [undefined, undefined] - * ``` - * - * @param size the size of the array; - * @param dv defaults value - * @returns - * @since 3.3.0 - */ -export const make = (size = 0, dv = undefined as unknown as a): a[] => { - const a = [] as a[]; - - while (a.length < size) { - a.push(dv as unknown as a); - } - - return a; -} -/** - * Maps an array of elements `a` to an array of elements `b` using the mapping function `f`. - * - * ```typescript - * import { Array } 'tiinvo'; - * - * console.log(Array.map(a => a + '!')(['a', 'b', 'c'])) // ['a!', 'b!', 'c!'] - * ``` - * - * @param f - * @returns - * @since 3.0.0 - */ -export const map = (f: f.map) => (a: a[]) => a.map(f); -/** - * Maps an array `a` by removing all elements that do not satisfy the predicate `p`. - * The filter occurs after mapping the elements. - * - * ```typescript - * import { Array, Number } 'tiinvo'; - * - * const fm = Array.filtermap(Number.umul(2), Number.gt(3)); - * - * fm([1, 2, 3]) // [4, 6] - * ``` - * - * @param f - * @param p - * @returns - * @since 3.6.0 - */ -export const mapfilter = (f: f.map, p: f.predicateE) => (a: a[]) => { - const r = [] as b[]; - - for (let i = 0; i < a.length; i++) { - const v = a[i]; - const m = f(v); - - if (p(m)) { - r.push(m); - } - } - - return r; -} -/** - * Returns true if all elements of `a` do not meet the condition specified in the predicate `p`. - * - * ```typescript - * import { Array, Number } 'tiinvo'; - * - * console.log(Array.none(Number.isEven)([1, 2, 3])) // false - * console.log(Array.none(Number.isEven)([1, 3, 5])) // true - * ``` - * - * @param p - * @returns - */ -export const none = (p: f.predicateE) => (a: a[]) => !a.some(p); -/** - * Returns a new array from a set of elements. - * - * ```typescript - * import { Array } 'tiinvo'; - * - * console.log(Array.of(1, 2, 3)) // [1, 2, 3] - * ``` - * - * @param a - * - * @returns - * @since 3.0.8 - */ -export const of = Array.of; -/** - * Returns a random element of an array `a`. - * - * ```typescript - * import { Array } 'tiinvo'; - * - * console.log(Array.random(['a', 'b', 'c'])) // 'a' or 'b' or 'c' - * ``` - * - * @param a - * @returns - * @since 3.0.0 - */ -export const rand = (a: a[]) => a[Math.floor(Math.random() * a.length)]; -/** - * Aggregates array of type `a[]` into a value of type `b` using a function `f`. - * - * ```typescript - * import { Array, Number } 'tiinvo'; - * - * const red = Array.reduce(Number.badd); - * - * console.log(red(0)([1, 2, 3])) // 6 - * ``` - * - * @param f - * @returns - * @since 3.0.0 - */ -export const reduce = (f: (p: b, c: a, i: number, al: a[]) => b) => (ac: b) => (a: a[]) => a.reduce(f, ac); -/** - * Aggregates array of type `a[]` into a value of type `b` using a function `f`. - * - * ```typescript - * import { Array, Number } 'tiinvo'; - * - * const red = Array.reduceright(Number.bsub); - * - * console.log(red(0)([1, 2, 3])) // -6 - * ``` - * - * @param f - * @returns - * @since 3.0.0 - */ -export const reduceright = (f: (p: b, c: a, i: number, al: a[]) => b) => (ac: b) => (a: a[]) => a.reduceRight(f, ac); -/** - * Reverses the elements in an array in place without mutating the original array. - * - * ```typescript - * import { Array } 'tiinvo'; - * - * console.log(Array.reverse([1, 2, 3])) // [3, 2, 1] - * ``` - * - * @param a - * @returns - * @since 3.0.0 - */ -export const reverse = (a: a[]) => { - const b: a[] = []; - for (let i = a.length; i > 0; i--) { b[a.length - 1 - i] = a[i]; } - return b -} -/** - * Shuffles an array - * - * ```typescript - * import { Array } 'tiinvo'; - * - * console.log(Array.shuffle([1, 2, 3])) // [1, 3, 2] - * ``` - * - * @param a - * @returns - * @since 3.0.0 - */ -export const shuffle = (a: a[]) => { - const b: a[] = [].slice.call(a); - - for (let i = b.length - 1; i > 0; i--) { - const j = Math.floor(Math.random() * (i + 1)); - [b[i], b[j]] = [b[j], b[i]]; - } - - return b; -} -/** - * Returns a slice of an array `a` from `start` to `end`. - * - * ```typescript - * - * import { Array } 'tiinvo'; - * - * console.log(Array.slice(1)([1, 2, 3, 4, 5])) // [2, 3, 4, 5] - * console.log(Array.slice(1, 3)([1, 2, 3, 4, 5])) // [2, 3] - * console.log(Array.slice(undefined, 3)([1, 2, 3, 4, 5])) // [1, 2, 3] - * ``` - * - * @param start - * @param end - * @returns - * @since 3.0.0 - */ -export const slice = (start: number | void, end: number | void) => (a: a[]) => a.slice(start ?? 0, end ?? a.length); -/** - * Determines whether some members of an array `a` satisfy the specified predicate `p`. - * - * ```typescript - * import { Array, Number } 'tiinvo'; - * - * const someeven = Array.some(Number.isEven); - * - * console.log(someeven([2, 4, 6])) // true - * console.log(someeven([2, 4, 5])) // true - * console.log(someeven([1, 3, 7])) // false - * ``` - * - * @param p - * @returns - * @since 3.0.0 - */ -export const some = (p: f.predicateE) => (a: a[]) => a.some(p); -/** - * Sorts an array of elements `a` using the specified comparator `f`. - * - * ```typescript - * import { Array, Number } 'tiinvo'; - * - * console.log(Array.sort(Number.asc)([3, 2, 1])) // [1, 2, 3] - * console.log(Array.sort(Number.desc)([1, 2, 3])) // [3, 2, 1] - * ``` - * - * @param f - * @returns - */ -export const sort = (f: f.comparableE) => (a: a[]) => a.sort(f); \ No newline at end of file diff --git a/src/assertable.ts b/src/assertable.ts deleted file mode 100644 index 7d9c99e..0000000 --- a/src/assertable.ts +++ /dev/null @@ -1,94 +0,0 @@ -import type * as f from './functors'; -import type * as r from './result'; - -/** - * Checks if a condition is true, otherwise throws an error. - * - * ```typescript - * import { Assert } from 'tiinvo'; - * - * Assert.check(true, 'This will not throw an error'); // undefined - * Assert.check(false, 'This will throw an error'); // throws an error - * ``` - * - * @param condition - * @param message - * @since 3.3.0 - */ -export const check = (condition: boolean, message?: string): void | never => { - if (!condition) { - throw new Error(message); - } -} - -/** - * Checks if a condition is true, otherwise returns a err. - * - * ```typescript - * import { Assert } from 'tiinvo'; - * - * Assert.checkr(true, 'This will not throw an error'); // undefined - * Assert.checkr(false, 'This will throw an error'); // Error('This will throw an error') - * ``` - * - * @param condition - * @param message - * @returns - * @since 3.3.0 - */ -export const checkr = (condition: boolean, message?: string): r.result => { - if (!condition) { - return new Error(message); - } -} -/** - * Returns a function that checks if a predicate is satisfied, otherwise throws an error. - * - * ```typescript - * import { Assert, Number } from 'tiinvo'; - * - * const check = Assert.checkr(Number.isEven, 'number is not even'); - * - * check(2); // undefined - * check(3); // throws Error('number is not even') - * ``` - * - * @param predicate - * @param message - * @returns - * @since 3.3.0 - */ -export const make = (predicate: f.predicateE, message?: string | ((a: a) => string)): (a: a) => void | never => { - return (a: a) => { - if (!predicate(a)) { - throw new Error(typeof message === 'function' ? message(a) : message); - } - } -} - -/** - * Returns a function that checks if a predicate is satisfied, otherwise returns a err. - * - * ```typescript - * import { Assert, Number } from 'tiinvo'; - * - * const check = Assert.checkr(Number.isEven, 'number is not even'); - * - * check(2); // undefined - * check(3); // Error('number is not even') - * ``` - * - * @param predicate - * @param message - * @returns - * @since 3.3.0 - */ -export const maker = (predicate: f.predicateE, message?: string | ((a: a) => string)): (a: a) => r.result => { - return (a: a) => { - if (!predicate(a)) { - return new Error(typeof message === 'function' ? message(a) : message); - } else { - return; - } - } -} diff --git a/src/bool.ts b/src/bool.ts index 5f4e4b6..d3ecf54 100644 --- a/src/bool.ts +++ b/src/bool.ts @@ -1,87 +1,38 @@ -import type * as f from './functors'; +import type * as Functors from './Functors.js'; -/** - * Compares two `booleans`. - * - * ```typescript - * import { Boolean } from 'bool' - * - * console.log(Boolean.cmp(true, true)) // 0 - * console.log(Boolean.cmp(true, false)) // 1 - * console.log(Boolean.cmp(false, true)) // -1 - * ``` - * - * @param a - * @param b - * @returns - * @since 3.0.0 - */ -export const cmp: f.comparableE = (a, b) => a > b ? 1 : a < b ? -1 : 0; +export type t = boolean; -/** - * Returns `true` if two booleans are equal, otherwise `false`. - * - * ```typescript - * import { Boolean } 'tiinvo'; - * - * console.log(Boolean.eq(true, true)) // true - * console.log(Boolean.eq(true, false)) // false - * console.log(Boolean.eq(false, true)) // false - * ``` - * - * @param a - * @param b - * @returns - * @since 3.0.0 - */ -export const eq: f.equatableE = (a, b) => a === b; +//#region guards /** - * Flips a boolean. - * - * ```typescript - * import { Boolean } 'tiinvo'; - * - * console.log(Boolean.flip(true)) // false - * console.log(Boolean.flip(false)) // true + * Checks if parameter `x` is `boolean` + * + * @example + * + * ```ts + * import { Bool } from 'tiinvo' + * + * Bool.guard(true) // true + * Bool.guard(1000) // false * ``` - * - * @param a - * @returns - * @since 3.0.0 + * + * @since 4.0.0 */ -export const flip = (a: boolean): boolean => !a; +export const guard: Functors.Guardable = (x: unknown): x is boolean => typeof x === 'boolean';; -/** - * Returns `true` if the given value is a `boolean`. - * - * ```typescript - * import { Boolean } from 'tiinvo' - * - * console.log(Boolean.guard(true)) // true - * console.log(Boolean.guard(false)) // true - * console.log(Boolean.guard(1)) // false - * console.log(Boolean.guard(null)) // false - * ``` - * - * @param a - * @returns - * @since 3.0.0 - */ -export const guard = (a => typeof a === 'boolean') as f.guard; +//#endregion /** - * Converts a `boolean` to a bit (`0 | 1`). - * - * ```typescript - * import { Boolean } from 'tiinvo' - * - * console.log(Boolean.toBit(true)) // 1 - * console.log(Boolean.toBit(false)) // 0 + * Flips a boolean value + * + * @example + * + * ```ts + * import { Bool } from 'tiinvo' + * + * Bool.flip(true) // false * ``` - * - * @param a - * @returns - * @since 3.0.0 + * + * @since 4.0.0 */ -export const toBit = (a: boolean) => a ? 1 : 0; +export const flip: Functors.Mappable = x => !x \ No newline at end of file diff --git a/src/date.ts b/src/date.ts deleted file mode 100644 index 8593811..0000000 --- a/src/date.ts +++ /dev/null @@ -1,553 +0,0 @@ -import * as f from './functors'; - -export enum Month { - January = 1, - February = 2, - March = 3, - April = 4, - May = 5, - June = 6, - July = 7, - August = 8, - September = 9, - October = 10, - November = 11, - December = 12, - Jan = 1, - Feb = 2, - Mar = 3, - Apr = 4, - Jun = 6, - Jul = 7, - Aug = 8, - Sep = 9, - Oct = 10, - Nov = 11, - Dec = 12, -} - -/** - * Compares two dates. - * - * ```typescript - * import { Date } from 'tiinvo/date'; - * - * const d1 = Date.make(2018, 0, 1); - * const d2 = Date.make(2018, 0, 2); - * - * d.cmp(d1, d2); // -1 - * d.cmp(d2, d1); // 1 - * d.cmp(d1, d1); // 0 - * ``` - * - * @param a - * @param b - * @returns - * @since 3.0.0 - */ -export const cmp: f.comparableE = (a, b) => a > b ? 1 : a < b ? -1 : 0; -/** - * Checks if a value `a` is a date. - * - * ```typescript - * import { Date } from 'tiinvo/date'; - * - * d.guard(Date.make(2018, 1, 1)); // true - * d.guard(Date.fromstring(`2018-01-01`)); // true - * d.guard({}); // false - * ``` - * - * @param a - * @since 3.0.0 - * - */ -export const guard = (a => Object.prototype.toString.call(a) === '[object Date]') as f.guard; -/** - * Checks if two dates are equal. - * - * ```typescript - * import { Date } from 'tiinvo/date'; - * - * const d1 = Date.make(2018, Date.Month.Jan, 1); - * const d2 = Date.make(2018, Date.Month.Jan, 1); - * const d3 = Date.make(2018, Date.Month.Feb, 1); - * - * d.eq(d1, d2); // true - * d.eq(d1, d3); // false - * ``` - * - * @param a - * @param b - * @returns - * @since 3.0.0 - */ -export const eq: f.equatableE = (a, b) => cmp(a, b) === 0; -/** - * Makes a date from a year, month and day. - * - * ```typescript - * import { Date } from 'tiinvo/date'; - * - * Date.make(2018, Date.Month.Jan, 1); // 2018-01-01T00:00:00.000Z - * ``` - * - * @param year - * @param month - * @param day - * @returns - * @since 3.0.0 - */ -export const make = (year: number, month: Month, day: number) => new Date(year, month - 1, day, 0, 0, 0, 0); - -/** - * Makes a date from a string - * - * ```typescript - * import { Date } from 'tiinvo/date'; - * - * Date.fromstr('2018-01-01'); // 2018-01-01T00:00:00.000Z - * ``` - * @param datestr - * @returns - * - * @since 3.0.10 - */ -export const fromstr = (datestr: `${string}-${string}-${string}`) => { - const [year, month, day] = datestr.split('T')[0].split('-'); - return make(Number(year), Number(month), Number(day)); -} - -//#region predicates - -/** - * Checks if a date `c` is between two dates `a` and `b` included. - * - * ```typescript - * import { Date } from 'tiinvo/date'; - * - * const d1 = Date.make(2018, 0, 1); - * const d2 = Date.make(2018, 0, 2); - * const d3 = Date.make(2018, 0, 3); - * - * Date.inrange(d1, d3)(d2); // true - * Date.inrange(d1, d3)(d1); // true - * Date.inrange(d1, d3)(d3); // true - * Date.inrange(d1, d2)(d3); // false - * ``` - * @param a - * @param b - * @returns - * @since 3.0.0 - */ -export const inrange = (a: Date, b: Date) => (c: Date) => cmp(c, a) >= 0 && cmp(c, b) <= 0; -/** - * Checks if a date is invalid - * - * ```typescript - * import { Date } from 'tiinvo/date'; - * - * d.invalid(new Date()); // false - * d.invalid(new Date(2018, 0, 1)); // false - * d.invalid(new Date(2018, 0, 1, 12, 0, 0)); // false - * d.invalid(new Date(`a`)); // true - * ``` - * - * @param date - * @returns - * @since 3.0.0 - */ -export const invalid = (date: Date) => isNaN(date.getTime()); -/** - * Checks if a date is in a leap year. - * - * ```typescript - * import { Date } from 'tiinvo/date'; - * - * console.log(Date.isleap(Date.make(2018, 0, 1))); // false - * console.log(Date.isleap(Date.make(2016, 0, 1))); // true - * ``` - * - * @param date - * @returns - * @since 3.0.0 - */ -export const isleap = (date: Date) => { - const year = date.getFullYear(); - return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0); -}; -/** - * Checks if a date `c` is not in a range between `a` and `b` included. - * - * ```typescript - * import { Date } from 'tiinvo/date'; - * - * const d1 = Date.make(2018, 0, 1); - * const d2 = Date.make(2018, 0, 2); - * const d3 = Date.make(2018, 0, 3); - * - * Date.notinrange(d1, d3)(d2); // false - * Date.notinrange(d1, d3)(d1); // false - * Date.notinrange(d1, d3)(d3); // false - * Date.notinrange(d1, d2)(d3); // true - * ``` - * - * @param a - * @param b - * @returns - * @since 3.0.0 - */ -export const notinrange = (a: Date, b: Date) => (c: Date) => cmp(c, a) < 0 || cmp(c, b) > 0; -/** - * Checks if a date is valid - * - * ```typescript - * import { Date as d } from 'tiinvo/date'; - * - * d.valid(new Date()); // true - * d.valid(new Date(2018, 0, 1)); // true - * d.valid(new Date(2018, 0, 1, 12, 0, 0)); // true - * d.valid(new Date('a')); // false - * ``` - * - * @param date - * @returns - * @since 3.0.0 - */ -export const valid = (date: Date) => !invalid(date); - -//#endregion - -//#region mutators - -/** - * Adds a number of years, months and days to a date without mutating the original date. - * All parameters are optional. - * - * ```typescript - * import { Date } from 'tiinvo/date'; - * - * const d1 = Date.make(2018, Date.Month.January, 1); - * Date.add(1, 1, 1)(d1); // 2019-02-02T00:00:00.000Z - * ``` - * - * @param year - * @param month - * @param day - * @returns - * @since 3.0.0 - */ -export const add = (year: number = 0, month: number = 0, day: number = 0) => (date: Date) => new Date(date.getFullYear() + year, date.getMonth() + month, date.getDate() + day); -/** - * Adds a number of years to a date without mutating the original date. - * - * ```typescript - * import * as d from 'tiinvo/date'; - * - * const d1 = Date.make(2018, Date.Month.January, 1); - * Date.addYears(1)(d1); // 2019-01-01T00:00:00.000Z - * ``` - * - * @param years - * @returns - * @since 3.0.0 - */ -export const addYears = (years: number) => add(years, 0, 0); -/** - * Adds a number of months to a date without mutating the original date. - * - * ```typescript - * import { Date } from 'tiinvo/date'; - * - * const d1 = Date.make(2018, Date.Month.January, 1); - * Date.addMonths(1)(d1); // 2018-02-01T00:00:00.000Z - * ``` - * - * @param months - * @returns - * @since 3.0.0 - */ -export const addMonths = (months: number) => add(0, months, 0); -/** - * Adds a number of days to a date without mutating the original date. - * - * ```typescript - * import * as Date from 'tiinvo/date'; - * - * const d1 = Date.make(2018, Date.Month.January, 1); - * Date.addDays(1)(d1); // 2018-01-02T00:00:00.000Z - * ``` - * @param days - * @returns - * @since 3.0.0 - */ -export const addDays = (days: number) => add(0, 0, days); -/** - * Subtracts a number of years, months and days from a date without mutating the original date. - * All parameters are optional. - * - * ```typescript - * import { Date } from 'tiinvo/date'; - * - * const d1 = Date.make(2018, Date.Month.January, 1); - * Date.sub(1, 1, 1)(d1); // 2017-11-30T00:00:00.000Z - * ``` - * - * @param year - * @param month - * @param day - * @returns - */ -export const sub = (year: number = 0, month: number = 0, day: number = 0) => add(-Math.abs(year), -Math.abs(month), -Math.abs(day)); -/** - * Subtracts a number of years from a date without mutating the original date. - * - * ```typescript - * import { Date } from 'tiinvo/date'; - * - * const d1 = Date.make(2018, Date.Month.January, 1); - * Date.subYears(1)(d1); // 2017-01-01T00:00:00.000Z - * ``` - * - * @param years - * @returns - * @since 3.0.0 - */ -export const subYears = (years: number) => sub(years, 0, 0); -/** - * Subtracts a number of months from a date without mutating the original date. - * - * ```typescript - * import { Date } from 'tiinvo/date'; - * - * const d1 = Date.make(2018, Date.Month.January, 1); - * Date.subMonths(1)(d1); // 2017-12-01T00:00:00.000Z - * ``` - * - * @param months - * @returns - * @since 3.0.0 - */ -export const subMonths = (months: number) => sub(0, months, 0); -/** - * Subtracts a number of days from a date without mutating the original date. - * - * ```typescript - * import { Date } from 'tiinvo/date'; - * - * const d1 = Date.make(2018, Date.Month.January, 1); - * Date.subDays(1)(d1); // 2017-12-31T00:00:00.000Z - * ``` - * - * @param days - * @returns - * @since 3.0.0 - */ -export const subDays = (days: number) => sub(0, 0, days); -/** - * Adds one year to a date without mutating the original date. - * - * ```typescript - * import * as d from 'tiinvo/date'; - * - * const d1 = d.make(2018, d.Month.January, 1); - * - * d.addYear(d1); // 2019-01-01T00:00:00.000Z - * ``` - * - * @param date - * @returns - * @since 3.0.0 - */ -export const addYear = addYears(1); -/** - * Adds one month to a date without mutating the original date. - * - * ```typescript - * import * as d from 'tiinvo/date'; - * - * const d1 = d.make(2018, d.Month.January, 1); - * - * d.addMonth(d1); // 2018-02-01T00:00:00.000Z - * ``` - * - * @param date - * @returns - * @since 3.0.0 - */ -export const addMonth = addMonths(1); -/** - * Adds one day to a date without mutating the original date. - * - * ```typescript - * import * as d from 'tiinvo/date'; - * - * const d1 = d.make(2018, d.Month.January, 1); - * - * d.addDay(d1); // 2018-01-02T00:00:00.000Z - * ``` - * - * @param date - * @returns - * @since 3.0.0 - */ -export const addDay = addDays(1); -/** - * Subtracts one year from a date without mutating the original date. - * - * ```typescript - * import * as d from 'tiinvo/date'; - * - * const d1 = d.make(2018, d.Month.January, 1); - * - * d.subYear(d1); // 2017-01-01T00:00:00.000Z - * ``` - * - * @param date - * @returns - * @since 3.0.0 - */ -export const subYear = subYears(1); -/** - * Subtracts one month from a date without mutating the original date. - * - * ```typescript - * import * as d from 'tiinvo/date'; - * - * const d1 = d.make(2018, d.Month.January, 1); - * - * d.subMonth(d1); // 2017-12-01T00:00:00.000Z - * ``` - * - * @param date - * @returns - * @since 3.0.0 - */ -export const subMonth = subMonths(1); -/** - * Subtracts one day from a date without mutating the original date. - * - * ```typescript - * import * as d from 'tiinvo/date'; - * - * const d1 = d.make(2018, d.Month.January, 1); - * - * d.subDay(d1); // 2017-01-01T00:00:00.000Z - * ``` - * - * @param date - * @returns - * @since 3.0.0 - */ -export const subDay = subDays(1); - -//#endregion - -//#region getters - -/** - * Gets the year of a date. - * - * ```typescript - * import { Date } from 'tiinvo/date'; - * - * const d1 = d.make(2018, d.Month.January, 1); - * - * d.year(d1); // 2018 - * ``` - * @param date - * @returns - * @since 3.0.0 - */ -export const getYear = (date: Date) => date.getFullYear(); -/** - * Gets the month of a date. - * - * ```typescript - * import { Date } from 'tiinvo/date'; - * - * const d1 = Date.make(2018, Date.Month.January, 1); - * - * Date.month(d1); // 1 - * ``` - * - * @param date - * @returns - * @since 3.0.0 - */ -export const getMonth = (date: Date): Month => date.getMonth() + 1; -/** - * Gets the day of a date. - * - * ```typescript - * import { Date } from 'tiinvo/date'; - * - * const d1 = Date.make(2018, Date.Month.January, 1); - * - * Date.day(d1); // 1 - * ``` - * - * @param date - * @returns - */ -export const getDay = (date: Date) => date.getDay(); - -//#endregion - -//#region sortables - -/** - * Used to sort dates in ascending order. - * - * ```typescript - * import { Date } from 'tiinvo/date'; - * - * const d1 = Date.make(2018, Date.Month.January, 1); - * const d2 = Date.make(2018, Date.Month.January, 2); - * Date.asc(d1, d2); // -1 - * ``` - * - * @param a - * @param b - * @returns - * @since 3.0.0 - */ -export const asc = (a: Date, b: Date) => { - const c = cmp(a, b); - return c === 1 ? -1 : c === -1 ? 1 : 0; -} - -/** - * Used to sort dates in descending order. - * - * ```typescript - * import * as d from 'tiinvo/date'; - * - * const d1 = Date.make(2018, Date.Month.January, 1); - * const d2 = Date.make(2018, Date.Month.January, 2); - * Date.desc(d1, d2); // 1 - * ``` - * - * @param a - * @param b - * @returns - * @since 3.0.0 - */ -export const desc = (a: Date, b: Date) => cmp(a, b); - -//#endregion - -/** - * Stringifies a date. - * - * ```typescript - * import { Date } from 'tiinvo/date'; - * - * const d1 = Date.make(2018, Date.Month.January, 1); - * Date.toString(d1); // 2018-01-01 - * ``` - * - * @param date - * @returns - * @since 3.0.0 - */ -export const toString = (date: Date) => `${date.getFullYear()}-${date.getMonth() + 1 >= 10 ? date.getMonth() + 1 : `0${date.getMonth() + 1}`}-${date.getDate() >= 10 ? date.getDate() : `0${date.getDate()}`}`; diff --git a/src/either.ts b/src/either.ts deleted file mode 100644 index ccbab24..0000000 --- a/src/either.ts +++ /dev/null @@ -1,541 +0,0 @@ -import type * as f from './functors'; -import type * as o from './option'; - -/** - * Represents a value of one of two possible types (a disjoint union). - */ -export type left = [a, o.none]; - -/** - * Represents a value of one of two possible types (a disjoint union). - */ -export type right = [o.none, b]; - -/** - * Represents a value of one of two possible types (a disjoint union). - */ -export type either = left | right; - -/** - * Extracts `left` type `a` - */ -export type leftType = a extends either ? b : null; - -/** - * Extracts `right` type `a` - */ -export type rightType = a extends either ? b : null; - -/** - * Checks if `a` is `left` - * - * ```typescript - * import { Either } from 'tiinvo'; - * - * expect(Either.isLeft([1, null])).toBe(true); - * expect(Either.isLeft([null, 2])).toBe(false); - * expect(Either.isLeft([null, null])).toBe(false); - * ``` - * - * @param a Either to check - * @returns `true` if `a` is `left` - * @since 3.0.0 - */ -export const isLeft = (a => Array.isArray(a) && a.length === 2 && a[0] !== undefined && a[0] !== null && (a[1] === undefined || a[1] === null)) as f.guard>; - -/** - * Checks if `b` is `left` if `b` is both `left` and it's value satisfies the `a` typeguard. - * - * ```typescript - * import { Either, Num } from 'tiinvo'; - * - * const isnumleft = Either.isLeftOf(Num.guard); - * - * expect(isnumleft([1, null])).toBe(true); - * expect(isnumleft(["hello world", null])).toBe(false); - * expect(isnumleft([null, 2])).toBe(false); - * expect(isnumleft([null, null])).toBe(false); - * ``` - * - * @param a The typeguard to check against - * @param b Either to check - * @returns `true` if `b` is `left` - * @since 3.0.0 - */ -export const isLeftOf = (a: f.guard) => (b: unknown): b is left => isLeft(b) && a(b[0]); - -/** - * Checks if `a` is `right` - * - * ```typescript - * import { Either } from 'tiinvo'; - * - * expect(Either.isRight([null, 2])).toBe(true); - * expect(Either.isRight([1, null])).toBe(false); - * expect(Either.isRight([null, null])).toBe(false); - * ``` - * - * @param a Either to check - * @returns `true` if `a` is `right` - * @since 3.0.0 - */ -export const isRight = (a => Array.isArray(a) && a.length === 2 && (a[0] === undefined || a[0] === null) && a[1] !== undefined && a[1] !== null) as f.guard>; - -/** - * Checks if `b` is `right` if `b` is both `right` and it's value satisfies the `a` typeguard. - * - * ```typescript - * import { Either, Number } from 'tiinvo'; - * - * const isnumRight = Either.isRightOf(Number.guard); - * - * expect(isnumright([null, 2])).toBe(true); - * expect(isnumright([1, null])).toBe(false); - * expect(isnumright([null, null])).toBe(false); - * ``` - * - * @param a The typeguard to check against - * @param b Either to check - * @returns `true` if `b` is `right` - * @since 3.0.0 - */ -export const isRightOf = (a: f.guard) => (b: unknown): b is right => isRight(b) && a(b[1]); - -/** - * Checks if `a` is `either` - * - * ```typescript - * import { Either } from 'tiinvo'; - * - * expect(Either.isEither([1, null])).toBe(true); - * expect(Either.isEither([null, 2])).toBe(true); - * expect(Either.isEither([null, null])).toBe(false); - * expect(Either.isEither(`hello world`)).toBe(false); - * ``` - * - * @param a Either to check - * @returns `true` if `a` is `either` - * @since 3.0.0 - */ -export const guard = (a => isLeft(a) || isRight(a)) as f.guard>; - -/** - * Checks if `c` is `either` and if `c` is `left` checks if it's value satisfies the `a` typeguard, otherwise if it's `right` checks if it's value satisfies the `b` typeguard. - * - * ```typescript - * import { Either, Number, String } from 'tiinvo'; - * - * const g = Either.guardOf(Number.guard, String.guard); - * - * expect(g([1, null])).toBe(true); - * expect(g([null, "hello world"])).toBe(true); - * expect(g([null, null])).toBe(false); - * expect(g([null, 1])).toBe(false); - * expect(g(["hello world", null])).toBe(false); - * ``` - * - * @param a The typeguard to check against if `c` is left - * @param b The typeguard to check against if `c` is right - * @param c Either to check - * - * @returns `true` if `c` is `either` and if `c` is `left` checks if it's value satisfies the `a` typeguard, otherwise if it's `right` checks if it's value satisfies the `b` typeguard. - * @since 3.0.0 - */ -export const guardOf = (a: f.guard, b: f.guard) => (c: unknown): c is either => guard(c) && (isLeftOf(a)(c) || isRightOf(b)(c)); - -/** - * Makes a `left` from `a` - * - * ```typescript - * import { Either } from 'tiinvo'; - * - * expect(Either.left(1)).toEqual([1, null]); - * ``` - * - * @param a The value to make `left` - * @returns `left` - * @since 3.0.0 - */ -export const left = (a: a): left => [a, null]; - -/** - * Makes a `right` from `b` - * - * ```typescript - * import { Either } from 'tiinvo'; - * - * expect(Either.right(1)).toEqual([null, 1]); - * ``` - * - * @param b The value to make `right` - * @returns `right` - */ -export const right = (b: b): right => [null, b]; - -/** - * Compares two `either`s - * - * ```typescript - * import { Either } from 'tiinvo'; - * - * expect(Either.cmp([1, null], [1, null])).toBe(0); - * expect(Either.cmp([1, null], [null, 1])).toBe(-1); - * ``` - * - * @param a The first `either` to compare - * @param b The second `either` to compare - * @returns `0` if `a` and `b` are equal, `-1` if `a` is less than `b`, `1` if `a` is greater than `b` - * @since 3.0.0 - */ -export const cmp: f.comparableE, either> = (a, b) => { - const ga = guard(a); - const gb = guard(b); - - if (ga && gb) { - const gla = isLeft(a); - const glb = isLeft(b); - const gra = isRight(a); - const grb = isRight(b); - - if (gla && glb) { - return a[0] === b[0] ? 0 : (a[0] as any) < (b[0] as any) ? -1 : 1; - } else if (gla && grb) { - return -1; - } else if (gra && glb) { - return 1; - } else { - return a[1] === b[1] ? 0 : (a[1] as any) < (b[1] as any) ? -1 : 1; - } - } else if (ga && !gb) { - return 1; - } else if (!ga && gb) { - return -1; - } else { - return 0; - } -} - -/** - * Returns `true` if the value of two eithers are equal - * - * ```typescript - * import { Either } from 'tiinvo'; - * - * expect(Either.eq([1, null], [1, null])).toBe(true); - * expect(Either.eq([2, null], [1, null])).toBe(false); - * expect(Either.eq([1, null], [null, 1])).toBe(false); - * ``` - * - * @param a The first `either` to compare - * @param b The second `either` to compare - * @returns `true` if the value of two eithers are equal - * - * @since 3.0.0 - */ -export const eq: f.equatableE> = (a, b) => cmp(a, b) === 0; - -/** - * Filters an `either` if it's `left` by a given predicate `f`. - * If the predicate returns `true` the `left` is kept, otherwise it's dropped. - * - * ```typescript - * import { Either } from 'tiinvo'; - * - * expect(Either.filterLeft(a => a === 1)([1, null])).toEqual([1, null]); - * expect(Either.filterLeft(a => a === 2)([1, null])).toEqual([null, null]); - * ``` - * - * @param f The predicate to filter by - * @param a The `either` to filter - * @returns `a` if `f` returns `true`, `left` otherwise - * - * @since 3.0.0 - */ -export const filterLeft = (f: f.predicateE) => (a: either) => isLeft(a) && f(a[0]) ? [a[0], null] : [null, a[1]]; - -/** - * Filters an `either` if it's `right` by a given predicate `f`. - * If the predicate returns `true` the `right` is kept, otherwise it's dropped. - * - * ```typescript - * import { Either } from 'tiinvo'; - * - * expect(Either.filterRight(a => a === 1)([null, 1])).toEqual([null, 1]); - * expect(Either.filterRight(a => a === 2)([null, 1])).toEqual([null, null]); - * ``` - * - * @param f The predicate to filter by - * @param a The `either` to filter - * @returns `a` if `f` returns `true`, `right` otherwise - * - * @since 3.0.0 - */ -export const filterRight = (f: f.predicateE) => (a: either) => isRight(a) && f(a[1]) ? [null, a[1]] : [a[0], null]; - -/** - * Folds an `either` into a single value `c` if it's either `left` or `right`, otherwise it's dropped. - * - * ```typescript - * import { Either } from 'tiinvo'; - * - * expect(Either.fold(a => a + 1, b => b * 2)([1, null])).toBe(2); - * expect(Either.fold(a => a + 1, b => b * 2)([null, 2])).toBe(4); - * ``` - * - * @param ml The `unary` function to fold `left` by - * @param mr The `unary` function to fold `right` by - * @param a The `either` to fold - * @returns The folded value `c` - * - * @since 3.0.0 - */ -export const fold = (ml: f.map, mr: f.map) => (a: either) => { - if (guard(a)) { - return isLeft(a) ? ml(a[0]) : mr(a[1]); - } - - throw new Error(`either.fold: either is neither left nor right`); -} - -/** - * Maps a `left` into a `left` by a given mapper `f` if `left`, otherwise it's dropped. - * - * ```typescript - * import { Either } from 'tiinvo'; - * - * expect(Either.mapLeft(a => a + 1)([1, null])).toEqual([2, null]); - * expect(Either.mapLeft(a => a + 1)([null, 2])).toEqual([null, null]); - * ``` - * - * @param f The mapper to map `left` by - * @param a The `either` to map `left` by - * @returns The mapped `left` - * - * @since 3.0.0 - */ -export const mapLeft = (m: f.map) => (a: either): either => isLeft(a) ? [m(a[0]), null] : [null, null]; - -/** - * Maps a `left` into a `left` by a given mapper `f` if `left`, otherwise returns a `left` with the specified fallback value `or`. - * - * ```typescript - * import { Either } from 'tiinvo'; - * - * expect(Either.mapLeftOr(0, a => a + 1)([1, null])).toEqual([2, null]); - * expect(Either.mapLeftOr(0, a => a + 1)([null, 2])).toEqual([0, null]); - * ``` - * - * @param or The fallback value to map `left` by - * @param f The mapper to map `left` by - * @param a The `either` to map `left` by - * @returns The mapped `left` - * - * @since 3.0.0 - */ -export const mapLeftOr = (or: b, m: f.map) => (a: either) => isLeft(a) ? [m(a[0]), null] : [or, null]; - -/** - * Maps a `left` into a `left` by a given mapper `f` if `left`, otherwise calls `or` and returns a `left` with the result. - * - * ```typescript - * import { Either } from 'tiinvo'; - * - * const or = () => 0; - * - * expect(Either.mapLeftOrElse(or, a => a + 1)([1, null])).toEqual([2, null]); - * expect(Either.mapLeftOrElse(or, a => a + 1)([null, 2])).toEqual([0, null]); - * ``` - * - * @param or The fallback nullary function to map `left` by - * @param f The mapper to map `left` by - * @param a The `either` to map `left` by - * @returns The mapped `left` - * - * @since 3.0.0 - */ -export const mapLeftOrElse = (or: f.map, m: f.map) => (a: either): either => isLeft(a) ? [m(a[0]), null] : [or(), null]; - -/** - * Maps a `right` into a `right` by a given mapper `f` if `right`, otherwise it's dropped. - * - * ```typescript - * import { Either } from 'tiinvo'; - * - * expect(Either.mapRight(a => a + 1)([null, 2])).toEqual([null, 3]); - * expect(Either.mapRight(a => a + 1)([1, null])).toEqual([null, null]); - * ``` - * - * @param f The mapper to map `right` by - * @param a The `either` to map `right` by - * @returns The mapped `right` - * - * @since 3.0.0 - */ -export const mapRight = (m: f.map) => (a: either): either => isRight(a) ? [null, m(a[1])] : [null, null]; - -/** - * Maps a `right` into a `right` by a given mapper `f` if `right`, otherwise returns a `right` with the specified fallback value `or`. - * - * ```typescript - * import { Either } from 'tiinvo'; - * - * expect(Either.mapRightOr(0, a => a + 1)([null, 2])).toEqual([null, 3]); - * expect(Either.mapRightOr(0, a => a + 1)([1, null])).toEqual([null, 0]); - * ``` - * - * @param or The fallback value to map `right` by - * @param f The mapper to map `right` by - * @param a The `either` to map `right` by - * @returns The mapped `right` - * - * @since 3.0.0 - */ -export const mapRightOr = (or: b, m: f.map) => (a: either): either => isRight(a) ? [null, m(a[1])] : [null, or]; - -/** - * Maps a `right` into a `right` by a given mapper `f` if `right`, otherwise calls `or` and returns a `right` with the result. - * - * ```typescript - * import { Either } from 'tiinvo'; - * - * const or = () => 0; - * - * expect(Either.mapRightOrElse(or, a => a + 1)([null, 2])).toEqual([null, 3]); - * expect(Either.mapRightOrElse(or, a => a + 1)([1, null])).toEqual([null, 0]); - * ``` - * - * @param or The fallback nullary function to map `right` by - * @param f The mapper to map `right` by - * @param a The `either` to map `right` by - * @returns The mapped `right` - * - * @since 3.0.0 - */ -export const mapRightOrElse = (or: f.map, m: f.map) => (a: either): either => isRight(a) ? [null, m(a[1])] : [null, or()]; - -/** - * Swaps the `left` and `right` of an `either`. - * - * ```typescript - * import { Either } from 'tiinvo'; - * - * expect(Either.swap(Either.left('a'))).toEqual(Either.right('a')); - * expect(Either.swap(Either.right('b'))).toEqual(Either.left('b')); - * ``` - * - * @param a The `either` to swap - * @returns The swapped `either` - * - * @since 3.0.0 - */ -export const swap = (either: either): either => isLeft(either) ? right(either[0]) : left(either[1]); - -/** - * Unwraps the `left` of an `either` if it's `left`, otherwise throws. - * - * ```typescript - * import { Either } from 'tiinvo'; - * - * expect(Either.unwrapLeft(Either.left('a')).toBe('a'); - * expect(() => Either.unwrapLeft(Either.right('b'))).toThrow(); - * ``` - * - * @param a The `either` to unwrap - * @returns The unwrapped `a` - * - * @since 3.0.0 - */ -export const unwrapLeft = (a: either): a | never => isLeft(a) ? a[0] : (() => { throw 'a is not left' })(); - -/** - * Unwraps the `left` of an `either` if it's `left`, otherwise returns the fallback value `or`. - * - * ```typescript - * import { Either } from 'tiinvo'; - * - * expect(Either.unwrapLeftOr('a', Either.right('b'))).toBe('a'); - * expect(Either.unwrapLeftOr('a', Either.left('b'))).toBe('b'); - * ``` - * - * @param or The fallback value to unwrap `left` by - * @param a The `either` to unwrap - * @returns The unwrapped `a` or the fallback value - * - * @since 3.0.0 - */ -export const unwrapLeftOr = (or: a) => (a: either) => isLeft(a) ? a[0] : or; - -/** - * Unwraps the `left` of an `either` if it's `left`, otherwise calls `or` and returns the result. - * - * ```typescript - * import { Either } from 'tiinvo'; - * - * const or = () => 'a'; - * - * expect(Either.unwrapLeftOrElse(or, Either.right('b'))).toBe('a'); - * expect(Either.unwrapLeftOrElse(or, Either.left('b'))).toBe('b'); - * ``` - * - * @param or The fallback nullary function to unwrap `left` by - * @param a The `either` to unwrap - * @returns The unwrapped `a` or the result of `or` - * - * @since 3.0.0 - */ -export const unwrapLeftOrElse = (or: f.map) => (a: either) => isLeft(a) ? a[0] : or(); - -/** - * Unwraps the `right` of an `either` if it's `right`, otherwise throws. - * - * ```typescript - * import { Either } from 'tiinvo'; - * - * expect(Either.unwrapRight(Either.right('b'))).toBe('b'); - * expect(() => Either.unwrapRight(Either.left('a'))).toThrow(); - * ``` - * - * @param a The `either` to unwrap - * @returns The unwrapped `b` - * - * @since 3.0.0 - */ -export const unwrapRight = (a: either): a | never => isRight(a) ? a[1] : (() => { throw 'a is not right' })(); - -/** - * Unwraps the `right` of an `either` if it's `right`, otherwise returns the fallback value `or`. - * - * ```typescript - * import { Either } from 'tiinvo'; - * - * expect(Either.unwrapRightOr('b', Either.left('a'))).toBe('b'); - * expect(Either.unwrapRightOr('b', Either.right('a'))).toBe('a'); - * ``` - * - * @param or The fallback value to unwrap `right` by - * @param a The `either` to unwrap - * @returns The unwrapped `b` or the fallback value - * - * @since 3.0.0 - */ -export const unwrapRightOr = (or: a) => (a: either): a => isRight(a) ? a[1] : or; - -/** - * Unwraps the `right` of an `either` if it's `right`, otherwise calls `or` and returns the result. - * - * ```typescript - * import { Either } from 'tiinvo'; - * - * const or = () => 'b'; - * - * expect(Either.unwrapRightOrElse(or, Either.left('a'))).toBe('b'); - * expect(Either.unwrapRightOrElse(or, Either.right('a'))).toBe('a'); - * ``` - * - * @param or The fallback nullary function to unwrap `right` by - * @param a The `either` to unwrap - * @returns The unwrapped `b` or the result of `or` - * - * @since 3.0.0 - */ -export const unwrapRightOrElse = (or: f.map) => (a: either): a => isRight(a) ? a[1] : or(); diff --git a/src/fn.ts b/src/fn.ts index eab13cf..3b3ff40 100644 --- a/src/fn.ts +++ b/src/fn.ts @@ -1,55 +1,213 @@ -export type argsOf any> = a extends (... args: infer A) => any ? A : never; - -export type argsOfMany any)[]> = [ - ... { - [k in keyof a]: a[k] extends ((i: infer b) => any) ? b : never - } -]; - -export type firstArgOf any> = a extends (first: infer A, ... args: any[]) => any ? A : void; - -export type lastArgOf any> = a extends (... args: infer A) => any ? A extends [... any, infer b] ? b : void : void; - -export type returnTypeOf any> = a extends (... args: any) => infer r ? r : void; - -export type returnTypeOfGuard any> = a extends (a: unknown) => a is infer r ? r : void; - -export type returnTypeOfMany any)[]> = [ - ... { - [k in keyof a]: a[k] extends ((i: any) => infer r) ? r : never - } -]; - -export type returnTypeOfManyGuards any)[]> = [ - ... { - [k in keyof a]: a[k] extends ((a: unknown) => a is infer r) ? r : never - } -]; - -export type anyfn = (... args: a) => b; - -export type nullary = () => a; - -export type unary = (a: a) => b; - -export type binary = (a: a, b: b) => c; - -export type ternary = (a: a, b: b, c: c) => d; - -export type quaternary = (a: a, b: b, c: c, d: d) => e; - -export type quinary = (a: a, b: b, c: c, d: d, e: e) => f; - -export type senary = (a: a, b: b, c: c, d: d, e: e, f: f) => g; - -export type septenary = (a: a, b: b, c: c, d: d, e: e, f: f, g: g) => h; - -export type octonary = (a: a, b: b, c: c, d: d, e: e, f: f, g: g, h: h) => i; - -export type nonary = (a: a, b: b, c: c, d: d, e: e, f: f, g: g, h: h, i: i) => j; - -export type decenary = (a: a, b: b, c: c, d: d, e: e, f: f, g: g, h: h, i: i, j: j) => k; - -export type nary = (... args: a extends [... infer b, any] ? b : [void]) => a extends [... any, infer c] ? c : void; - -export type asyncFnReturnType Promise> = a extends (... arg: any[]) => Promise ? r : never; +import type * as Functors from './Functors.js'; + +//#region types + +/** + * Represents any async function + */ +export type AnyAsyncFn = (...args: any[]) => Promise; + +/** + * Represents any function + */ +export type AnyFn = (...args: any[]) => any; + +/** + * Represents any unary function + */ +export type Unary = (a: a) => r; + +/** + * Represents any binary function + */ +export type Binary = (a: a, b: b) => r; + +/** + * Represents any ternary function + */ +export type Ternary = (a: a, b: b, c: c) => r; + +/** + * Represents any quaternary function + */ +export type Quaternary = (a: a, b: b, c: c, d: d) => r; + +/** + * Represents any quinary function + */ +export type Quinary = (a: a, b: b, c: c, d: d, e: e) => r; + +/** + * Represents any senary function + */ +export type Senary = (a: a, b: b, c: c, d: d, e: e, f: f) => r; + +/** + * Represents any septenary function + */ +export type Septenary = (a: a, b: b, c: c, d: d, e: e, f: f, g: g) => r; + +/** + * Represents any octonary function + */ +export type Octonary = (a: a, b: b, c: c, d: d, e: e, f: f, g: g, h: h) => r; + +/** + * Represents any nonary function + */ +export type Nonary = (a: a, b: b, c: c, d: d, e: e, f: f, g: g, h: h, i: i) => r; + +/** + * Represents any decenary function + */ +export type Decenary = (a: a, b: b, c: c, d: d, e: e, f: f, g: g, h: h, i: i, l: l) => r; + +//#endregion + +/** + * A function which returns it's first argument. + * + * Use it only as a placeholder + * + * @example + * + * ```ts + * import { Fn } from 'tiinvo' + * + * Fn.pass(10) // 10 + * Fn.pass(20) // 20 + * ``` + * + * @since 4.0.0 + */ +export const pass = (a: a) => a; + +//#region comparables + +/** + * Compares two function signatures and names. + * + * @example + * + * ```ts + * import { Fn, Num } from 'tiinvo' + * + * Fn.cmp(Num.add, Num.add) // 0 + * Fn.cmp(Num.add, Num.sub) // -1 + * Fn.cmp(Num.sub, Num.add) // 1 + * ``` + * + * @since 4.0.0 + */ +export const cmp: Functors.Comparable = (a, b) => { + const namea = a.name.toLowerCase(); + const nameb = b.name.toLowerCase(); + + return Math.min( + Math.max( + (a.length > b.length ? 1 : a.length < b.length ? -1 : 0) + + (namea > nameb ? 1 : namea < nameb ? -1 : 0), + -1), + 1 + ) as -1 | 0 | 1; +}; + +/** + * Checks if two functions are the same. + * + * If will check that both function have: + * + * - the same list of arguments + * - the same name + * - the address in memory + * + * @example + * + * ```ts + * import { Fn, Num } from 'tiinvo' + * + * Fn.eq(Num.add, Num.add) // true + * Fn.eq(Num.add, Num.sub) // false + * Fn.eq(Num.sub, Num.add) // false + * ``` + * + * @since 4.0.0 + */ +export const eq: Functors.Equatable = (a, b) => cmp(a, b) === 0 && a === b; + +//#endregion + +//#region mappables + +/** + * Returns a function arguments length + * + * @example + * + * ```ts + * import { Fn } from 'tiinvo' + * + * Fn.length(Fn.cmp) // 2 + * Fn.length(Fn.length) // 1 + * ``` + * + * @since 4.0.0 + */ +export const length: Functors.Mappable = x => x.length; + +/** + * Returns a function's name + * + * @example + * + * ```ts + * import { Fn } from 'tiinvo' + * + * Fn.name(Fn.cmp) // 'cmp' + * Fn.name(Fn.name) // 'name' + * ``` + * + * @since 4.0.0 + */ +export const name: Functors.Mappable = x => x.name; + +//#region guardables + +/** + * Checks if an argument `x` is `AnyFn` + * + * @example + * + * ```ts + * import { Fn } from 'tiinvo' + * + * Fn.guard(10) // false + * Fn.guard(() => {}) // true + * ``` + * + * @since 4.0.0 + */ +export const guard = (x: unknown): x is AnyFn => typeof x === 'function'; + +//#endregion + +//#region mappables + +/** + * Maps a value `a` over a list of `Functors.Mappable` and returns an array of they returning values + * + * @example + * + * ```ts + * import { Fn, Num } from 'tiinvo' + * + * const m = Fn.map(Num.add(1), Num.mul(2), Num.sub(3), Num.pow(4)) + * + * m(2) // [3, 4, -1, 16] + * ``` + * + * @since 4.0.0 + */ +export const map = (...ml: Functors.Mappable[]) => (a: a) => ml.map(f => f(a)); + +//#endregion diff --git a/src/function.ts b/src/function.ts deleted file mode 100644 index 57c8b0d..0000000 --- a/src/function.ts +++ /dev/null @@ -1,56 +0,0 @@ -import type * as f from './functors'; -import type * as fn from './fn'; - -export const bind = (f: f, ... args: fn.argsOf): fn.nullary> => () => f(... args as any); - -export const call = (f: f, ... args: fn.argsOf): fn.returnTypeOf => f(...args as any); - -export const cmp: f.comparableE = (a, b) => { - const namea = String(a.name).toLowerCase(); - const nameb = String(b.name).toLowerCase(); - const n = namea < nameb ? -1 : namea > nameb ? 1 : 0; - return a.length === b.length ? n : a.length < b.length ? -1 : 1; -}; - -export const eq: f.equatableE = (a, b) => cmp(a, b) === 0; - -export const name = (f: f): string => f.name; - -export const guard = (a => typeof a === 'function') as f.guard>; - -export const curry2 = >(f: a) => (a: fn.argsOf[0]) => (b: fn.argsOf[1]): fn.returnTypeOf => f(a, b); - -export const curry3 = >(f: a) => (a: fn.argsOf[0]) => (b: fn.argsOf[1]) => (c: fn.argsOf[2]): fn.returnTypeOf => f(a, b, c); - -export const curry4 = >(f: a) => (a: fn.argsOf[0]) => (b: fn.argsOf[1]) => (c: fn.argsOf[2]) => (d: fn.argsOf[3]): fn.returnTypeOf => f(a, b, c, d); - -export const curry5 = >(f: a) => (a: fn.argsOf[0]) => (b: fn.argsOf[1]) => (c: fn.argsOf[2]) => (d: fn.argsOf[3]) => (e: fn.argsOf[4]): fn.returnTypeOf => f(a, b, c, d, e); - -export const curry6 = >(f: a) => (a: fn.argsOf[0]) => (b: fn.argsOf[1]) => (c: fn.argsOf[2]) => (d: fn.argsOf[3]) => (e: fn.argsOf[4]) => (g: fn.argsOf[5]): fn.returnTypeOf => f(a, b, c, d, e, g); - -export const curry7 = >(f: a) => (a: fn.argsOf[0]) => (b: fn.argsOf[1]) => (c: fn.argsOf[2]) => (d: fn.argsOf[3]) => (e: fn.argsOf[4]) => (g: fn.argsOf[5]) => (h: fn.argsOf[6]): fn.returnTypeOf => f(a, b, c, d, e, g, h); - -export const curry8 = >(f: a) => (a: fn.argsOf[0]) => (b: fn.argsOf[1]) => (c: fn.argsOf[2]) => (d: fn.argsOf[3]) => (e: fn.argsOf[4]) => (g: fn.argsOf[5]) => (h: fn.argsOf[6]) => (i: fn.argsOf[7]): fn.returnTypeOf => f(a, b, c, d, e, g, h, i); - -export const curry9 = >(f: a) => (a: fn.argsOf[0]) => (b: fn.argsOf[1]) => (c: fn.argsOf[2]) => (d: fn.argsOf[3]) => (e: fn.argsOf[4]) => (g: fn.argsOf[5]) => (h: fn.argsOf[6]) => (i: fn.argsOf[7]) => (j: fn.argsOf[8]): fn.returnTypeOf => f(a, b, c, d, e, g, h, i, j); - -export const curry10 = >(f: a) => (a: fn.argsOf[0]) => (b: fn.argsOf[1]) => (c: fn.argsOf[2]) => (d: fn.argsOf[3]) => (e: fn.argsOf[4]) => (g: fn.argsOf[5]) => (h: fn.argsOf[6]) => (i: fn.argsOf[7]) => (j: fn.argsOf[8]) => (k: fn.argsOf[9]): fn.returnTypeOf => f(a, b, c, d, e, g, h, i, j, k); - -export const pass = (a: a) => a; - -/** - * Maps an array of functions `a` over an argument `b`. - * - * ```ts - * import { Function, Number, String, Array } from 'tiinvo' - * - * const m = Function.map(String.chars, pipe(String.chars, Array.map(String.upper))); - * - * m('abc') // [['a', 'b', 'c'], ['A', 'B', 'C']] - * ``` - * - * @param a - * @returns - * @since 3.2.0 - */ -export const map = (... a: fn.unary[]) => (arg: a) => a.map(f => f(arg)); diff --git a/src/functors.ts b/src/functors.ts index 1ca6c1c..9d9eb21 100644 --- a/src/functors.ts +++ b/src/functors.ts @@ -1,23 +1,218 @@ -export type comparable = (a: a, b: b) => -1 | 0 | 1; +import type * as Fn from './Fn.js'; -export type comparableE = (a: a, b: b) => -1 | 0 | 1; +/** + * Represents a builder (or factory) function. + */ +export type Buildable = (a?: Partial) => a; -export type equatable = (a: a, b: a) => boolean; +/** + * Represents a builder (or factory) module. + */ +export type BuildableMobule = { + make: Buildable; +}; -export type equatableE = (a: a, b: a) => boolean; +/** + * Represents a builder (or factory) module with a `guard` function. + */ +export type ModuleSafeBuildable = BuildableMobule & Guardable; -export type guard = (a: unknown) => a is a; +/** + * Represents a functor which generates a unique id for a given parameter `a` + */ +export type Identifiable = (a: a) => i; -export type predicate = (value: a) => boolean; +/** + * Represents a functor module which generates a unique id for a given parameter `a` + */ +export type IdentifiableModule = { + id: Identifiable; +}; -export type predicateA = (value: any) => boolean; +export const defaultsymbol = Symbol('default') -export type predicateE = (value: a) => boolean; +/** + * + */ +export type DefaultableModule = { + [defaultsymbol]: a; +}; -export type map = (a: a) => b; +//#region catchables -export type unwrappable = (value: a) => a | never; +export const catchableAsync = Symbol("catchable.async"); +export const catchableSync = Symbol("catchable.sync"); -export type unwrappableOr = (or: a) => (value: a) => a; +/** + * Represents a synchronous catchable functor + */ +export type Catchable = { + catch(error: Error, args: Parameters): ReturnType; + func: f; +}; -export type unwrappableOrElse = (or: map) => (value: a) => a; +export type CatchableAsyncModule = { + [catchableAsync](): Catchable; +}; + +export type CatchableSyncModule = { + [catchableSync](): Catchable; +}; + +/** + * + */ +export type CatchableModule = CatchableAsyncModule | CatchableSyncModule; + +//#endregion + +//#region comparables + +/** + * + */ +export type ComparableResult = -1 | 0 | 1; + +/** + * + */ +export type Comparable = (a: a, b: a) => ComparableResult; + +/** + * + */ +export type ComparableModule = { + cmp: Comparable; +}; + +/** + * + */ +export type Equatable = (a: a, b: a) => boolean; + +/** + * + */ +export type EquatableModule = { + eq: Equatable; +}; + +/** + * + */ +export type EquatableOverloaded = (a: a) => (b: a) => boolean; + +/** + * + */ +export type EquatableOverloadedModule = { + eq: EquatableOverloaded; +}; + +/** + * + */ +export type AnyEquatable = Equatable | EquatableOverloaded; + +/** + * + */ +export type AnyEquatableModule = { + eq: Equatable | EquatableOverloaded; +}; + +//#endregion + +//#region guardables + +/** + * + */ +export type Guardable = (x: unknown) => x is a; + +/** + * + */ +export type GuardableModule = { + guard: Guardable; +}; + +//#endregion + +//#region filterables + +/** + * + */ +export type Filterable = (a: a) => boolean; + +/** + * + */ +export type FilterableModule = { + filter: Filterable; +}; + +//#endregion + +//#region mappables + +/** + * + */ +export type Mappable = (a: a) => b; + +/** + * + */ +export type MappableModule = { + map: Mappable; +}; + +/** + * + */ +export type Reduceable = (p: b, c: a) => b; + +/** + * + */ +export type ReduceableModule = { + reduce: Reduceable; +}; + +/** + * + */ +export interface Operable { + (a: a, b: a): a; + (a: a): (b: a) => a; +} + +/** + * + */ +export type OperableModule = { + add: Operable; + div: Operable; + mul: Operable; + pow: Operable; + root: Operable; + sub: Operable; +}; + +//#endregion + +//#region composites + +/** + * Compound module of `ModuleFilterable` and `ModuleMappable` + */ +export type FilterMappableModule = FilterableModule & MappableModule; + +/** + * + */ +export type FilterReduceableModule = FilterableModule & ReduceableModule & DefaultableModule; + +//#endregion \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 439ee01..e73e8f7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,50 +1,35 @@ -import * as Assert from './assertable'; -import * as Arr from './array'; -import * as Bool from './bool'; -import * as Dt from './date'; -import * as Either from './either'; -import type * as Fn from './fn'; -import * as Func from './function'; -import type * as Functors from './functors'; -import * as Maybe from './maybe'; -import * as Num from './num'; -import * as Obj from './obj'; -import * as Option from './option'; -import * as PipeAsync from './pipe.async'; -import * as Pipe from './pipe'; -import * as Predicate from './predicate'; -import * as Prms from './promise'; -import * as Result from './result'; -import * as Set from './set'; -import * as Str from './str'; -import * as Try from './try'; -import * as Tuple from './tuple'; +import * as Arr from './Arr.js'; +import * as Assert from './Assert.js'; +import * as Bool from './Bool.js'; +import * as Catch from './Catch.js'; +import * as Fn from './Fn.js'; +import * as Functors from './Functors.js'; +import * as Num from './Num.js'; +import * as Obj from './Obj.js'; +import * as Option from './Option.js'; +import * as Pipe from './Pipe.js'; +import * as Predicate from './Predicate.js'; +import * as Result from './Result.js'; +import * as Sequence from './Sequence.js'; +import * as SortedSequence from './SortedSequence.js'; +import * as Str from './Str.js'; +import * as TypedSequence from './TypedSequence.js'; export { - Assert, Arr, - Arr as Array, + Assert, Bool, - Bool as Boolean, - Dt as Date, - Either, + Catch, Fn, - Func as Function, Functors, - Maybe, Num, - Num as Number, Obj, - Obj as Object, Option, - PipeAsync, Pipe, Predicate, - Prms as Promise, Result, - Set, + Sequence, + SortedSequence, Str, - Str as String, - Try, - Tuple, -} \ No newline at end of file + TypedSequence, +}; diff --git a/src/maybe.ts b/src/maybe.ts deleted file mode 100644 index dbaaf47..0000000 --- a/src/maybe.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type * as f from './functors' - -export type just = a; - -export type nothing = null | undefined; - -export type maybe = just | nothing; - -export const isJust = (value: maybe): value is just => !!value; - -export const isNothing = (value: maybe): value is nothing => !value; - -export const isMaybeOf = (guard: f.guard) => (value: unknown): value is maybe => isJust(value) ? guard(value) : true; - -export const cmp: f.comparable = (value: maybe, value2: maybe) => isNothing(value) && isNothing(value2) ? 0 : isNothing(value) ? -1 : isNothing(value2) ? 1 : value as any === value2 ? 0 : value as any < value2 ? -1 : 1; - -export const eq: f.equatable = (value: maybe, value2: maybe) => isJust(value) && isJust(value2) ? value as any === value2 : isNothing(value) && isNothing(value2); - -export const filter = (predicate: f.predicateE) => (value: maybe): maybe => isJust(value) && predicate(value) ? value : null as nothing; - -export const map = (map: f.map) => (value: maybe) => isJust(value) ? map(value) : null; - -export const mapOr = (or: b, map: f.map) => (value: maybe) => isJust(value) ? map(value) : or; - -export const mapOrElse = (or: f.map, map: f.map) => (value: maybe) => isJust(value) ? map(value) : or(); - -export const unwrap: f.unwrappable = value => isJust(value) ? value : (() => {throw new Error('cannot unwrap nothing')})(); - -export const unwrapOr: f.unwrappableOr = or => value => isJust(value) ? value : or; - -export const unwrapOrElse: f.unwrappableOrElse = orElse => value => isJust(value) ? value : orElse(); diff --git a/src/num.ts b/src/num.ts index 07d5322..5f895da 100644 --- a/src/num.ts +++ b/src/num.ts @@ -1,522 +1,758 @@ -import type * as f from './functors'; -import type * as fn from './fn'; +import type * as Fn from './Fn.js'; +import type * as Functors from './Functors.js'; + +export type t = number; + +//#region guards /** - * Checks if the given value is a number. + * Checks (at compile and runtime) if a given parameter `x` is a `number` + * + * @example * - * ```typescript - * import { Number } from 'tiinvo'; + * ```ts + * import { Num } from 'tiinvo' * - * console.log(Number.guard(10)) // true - * console.log(Number.guard('')) // false + * const or0 = (x: unknown): t => Num.guard(x) ? x : 0; + * + * or0(10) // 10 + * or0(20) // 20 + * or0(-1) // -1 + * or0(4e12) // 4e12 + * or0('hello world') // 0 + * or0(true) // 0 + * or0(false) // 0 + * or0({}) // 0 * ``` * - * @since 3.0.0 + * @since 4.0.0 */ -export const guard = (value => typeof value === 'number') as f.guard; +export const guard: Functors.Guardable = (x): x is t => typeof (x) === 'number'; + +//#endregion + +//#region comparables /** - * Compares two numbers. + * Compares two numbers `a` and `b`. * - * ```typescript - * import { Number } from 'tiinvo'; + * Returns: * - * console.log(Number.compare(10, 5)) // 1 - * console.log(Number.compare(5, 10)) // -1 - * console.log(Number.compare(5, 5)) // 0 - * ``` + * - 1 if `a` is greater than `b` + * - 0 if `a` is same of `b` + * - -1 if `b` is greater than `a` + * + * @example + * + * ```ts + * import { Num } from 'tiinvo'; * - * @param a - * @param b - * @returns + * Num.cmp(1, 1) // 0 + * Num.cmp(1, 0) // 1 + * Num.cmp(0, 1) // -1 + * ``` + * + * @since 4.0.0 */ -export const cmp: f.comparableE = (a, b): -1 | 0 | 1 => a === b ? 0 : a < b ? -1 : 1; +export const cmp: Functors.Comparable = (a, b) => a > b ? 1 : a < b ? -1 : 0; /** - * Returns the integral part of the a numeric expression, `a`, removing any fractional digits. If `a` is already an integer, the result is `a`. + * Returns `true` if two numbers are the same * - * ```typescript - * import { Number } from 'tiinvo'; + * ```ts + * import { Num } from 'tiinvo'; * - * console.log(Number.int(10.5)) // 10 - * console.log(Number.int(10.1)) // 10 - * console.log(Number.int(10.9)) // 10 - * console.log(Number.int(10)) // 10 + * Num.eq(1, 1) // true + * Num.eq(1, 0) // false + * Num.eq(0, 1) // false * ``` - * - * @param a - * @returns - * @since 3.1.0 + * + * @since 4.0.0 */ -export const int = (a: number): number => a | 0; +export function eq(a: t, b: t): boolean; +export function eq(a: t): Fn.Unary; +export function eq(a: t, b?: t): any { + return guard(a) && guard(b) ? a === b : (c: t) => eq(a, c); +}; /** - * Returns true if is even + * Returns true if `a` is greater than `b` if `b` is specified, otherwise returns a + * function which once called returns true if `b` is greater than `a` * - * ```typescript - * import { Number } from 'tiinvo'; + * @example * - * console.log(Number.isEven(10)) // true - * console.log(Number.isEven(11)) // false - * ``` + * ```ts + * import { Num } from 'tiinvo'; + * + * Num.gt(5, -2) // true + * Num.gt(5, 12) // false + * + * const gt5 = Num.gt(5) * - * @param n - * @returns + * gt5(10) // true + * gt5(-2) // false + * ``` */ -export const isEven: f.predicateE = n => n % 2 === 0; +export function gt(a: t, b: t): boolean; +export function gt(a: t): Fn.Unary; +export function gt(a: t, b?: any): any { + if (guard(b)) { + return a > b; + } + + return (b: any) => b > a; +} /** - * Returns true if is odd + * Returns true if `a` is lesser than `b` if `b` is specified, otherwise returns a + * function which once called returns true if `b` is lesser than `a` * - * ```typescript - * import { Number } from 'tiinvo'; + * @example * - * console.log(Number.isOdd(10)) // false - * console.log(Number.isOdd(11)) // true - * ``` + * ```ts + * import { Num } from 'tiinvo'; + * + * Num.lt(5, -2) // false + * Num.lt(5, 12) // true * - * @param n - * @returns + * const lt5 = Num.lt(5) + * + * lt5(10) // true + * lt5(-2) // false + * ``` */ -export const isOdd: f.predicateE = n => n % 2 !== 0; +export function lt(a: t, b: t): boolean; +export function lt(a: t): Fn.Unary; +export function lt(a: t, b?: any): any { + if (guard(b)) { + return a < b; + } -//#region sortables + return (b: any) => b < a; +} /** - * Used to sort numbers asceding. + * Returns true if `a` is great or equal to `b` if `b` is specified, otherwise returns a + * function which once called returns true if `b` is great or equal to `a` + * + * @example + * + * ```ts + * import { Num } from 'tiinvo'; * - * ```typescript - * import { Number } from 'tiinvo'; + * Num.gte(5, -2) // true + * Num.gte(5, 12) // false + * Num.gte(10, 10) // true * - * const array = [10, 5, 3, 1]; - * console.log(array.sort(n.asc)) // [1, 3, 5, 10] + * const gte5 = Num.gte(5) + * + * gte5(10) // false + * gte5(5) // false + * gte5(-2) // true * ``` - * @param a - * @param b - * @returns */ -export const asc: f.comparableE = (a, b) => cmp(a, b); +export function gte(a: t, b: t): boolean; +export function gte(a: t): Fn.Unary; +export function gte(a: t, b?: any): any { + if (guard(b)) { + return a >= b; + } + + return (c: any) => c >= a; +} /** - * Used to sort numbers descending. + * Returns true if `a` is less or equal to `b` if `b` is specified, otherwise returns a + * function which once called returns true if `b` is less or equal to `a` + * + * @example + * + * ```ts + * import { Num } from 'tiinvo'; + * + * Num.lte(5, -2) // false + * Num.lte(5, 12) // true + * Num.lte(5, 5) // true * - * ```typescript - * import { Number } from 'tiinvo'; + * const lte5 = Num.lte(5) * - * const array = [1, 5, 3, 10]; - * console.log(array.sort(n.desc)) // [10, 5, 3, 1] + * lte5(5) // true + * lte5(10) // false + * lte5(-2) // true * ``` + */ +export function lte(a: t, b: t): boolean; +export function lte(a: t): Fn.Unary; +export function lte(a: t, b?: any): any { + if (guard(b)) { + return a <= b; + } + + return (b: any) => b <= a; +} + +/** + * Returns true if `a` not equal to `b` if `b` is specified, otherwise returns a + * function which once called returns true if `b` not equal to `a` + * + * @example * - * @param a - * @param b - * @returns + * ```ts + * import { Num } from 'tiinvo'; + * + * Num.ne(5, -2) // true + * Num.ne(5, 12) // true + * Num.ne(5, 5) // false + * + * const ne5 = Num.ne(5) + * + * ne5(5) // false + * ne5(10) // true + * ne5(-2) // true + * ``` */ -export const desc: f.comparableE = (a, b) => cmp(b, a); +export function ne(a: t, b: t): boolean; +export function ne(a: t): Fn.Unary; +export function ne(a: t, b?: any): any { + if (guard(b)) { + return a !== b; + } -//#endregion + return (b: any) => b !== a; +} -//#region comparators +//#endregion -export type unarypredicate = fn.unary>; +//#region mappables /** - * Returns true if two numbers are equal + * Maps a number `a` to a value `Result.t` if a is `number`, otherwise returns `Err`. * - * ```typescript - * import { Number } from 'tiinvo'; + * ```ts + * import { Num } from 'tiinvo'; * - * console.log(Number.eq(10, 10)) // true - * console.log(Number.eq(10, 11)) // false - * ``` + * const toHex = Num.map(x => '0x' + x.toString(16)) * - * @param a - * @returns + * toHex(10) // 0xa + * toHex("a") // Error("a is not a number") + * ``` */ -export const eq: unarypredicate = a => b => cmp(a, b) === 0; +export const map = (m: Functors.Mappable) => (a: t) => guard(a) ? m(a) : new Error("a is not a number"); + /** - * Returns true if b is greater or equal to a + * Maps a number `a` to a value `Result.t` if a is `number`, otherwise returns `b`. * - * ```typescript - * import { Number } from 'tiinvo'; + * ```ts + * import { Num } from 'tiinvo'; * - * console.log(Number.gte(10)(10)) // true - * console.log(Number.gte(10)(11)) // true - * console.log(Number.gte(10)(9)) // false - * ``` - * @param a - * @returns + * const toHex = Num.mapOr(x => '0x' + x.toString(16), "0x0") + * + * toHex(10) // 0xa + * toHex("a") // 0x0 + * ``` */ -export const ge: unarypredicate = a => b => cmp(a, b) <= 0; +export const mapOr = (m: Functors.Mappable, b: b) => (a: t) => guard(a) ? m(a) : b; + +//#endregion + +//#region predicates + /** - * Returns true if b is greater than a + * Returns true if a number `x` is even. * - * ```typescript - * import { Number } from 'tiinvo'; + * @example * - * console.log(Number.gt(10)(10)) // false - * console.log(Number.gt(10)(11)) // true - * console.log(Number.gt(10)(9)) // false + * ```ts + * import { Num } from 'tiinvo'; + * + * Num.isEven(10) // true + * Num.isEven(91) // false * ``` * - * @param a - * @returns + * @since 4.0.0 */ -export const gt: unarypredicate = a => b => cmp(a, b) === -1; +export const isEven: Functors.Filterable = x => x % 2 === 0; + /** - * Returns true if b is less or equal to a + * Returns true if a number `x` is odd. + * + * @example * - * ```typescript - * import { Number } from 'tiinvo'; + * ```ts + * import { Num } from 'tiinvo'; * - * console.log(Number.lt(5)(10)) // false - * console.log(Number.lt(10)(10)) // true - * console.log(Number.lt(10)(5)) // true + * Num.isOdd(10) // false + * Num.isOdd(91) // true * ``` * - * @param a - * @returns + * @since 4.0.0 */ -export const le: unarypredicate = a => b => cmp(a, b) >= 0; +export const isOdd: Functors.Filterable = x => x % 2 !== 0; + /** - * Returns true if b is less than a + * Returns true if a number `x` is positive. + * + * @example * - * ```typescript - * import { Number } from 'tiinvo'; + * ```ts + * import { Num } from 'tiinvo'; * - * console.log(Number.lt(5)(10)) // false - * console.log(Number.lt(10)(10)) // false - * console.log(Number.lt(10)(5)) // true + * Num.isNegative(-1) // true + * Num.isNegative(10) // false * ``` * - * @param a - * @returns + * @since 4.0.0 */ -export const lt: unarypredicate = a => b => cmp(a, b) === 1; +export const isNegative: Functors.Filterable = x => x < 0; + /** - * Returns true if b is not equal to a + * Returns true if a number `x` is positive. + * + * @example * - * ```typescript - * import { Number } from 'tiinvo'; + * ```ts + * import { Num } from 'tiinvo'; * - * console.log(Number.ne(10)(10)) // false - * console.log(Number.ne(10)(11)) // true + * Num.isPositive(-1) // false + * Num.isPositive(10) // true * ``` * - * @param a - * @returns + * @since 4.0.0 */ -export const ne: unarypredicate = a => b => cmp(a, b) !== 0; +export const isPositive: Functors.Filterable = x => x >= 0; //#endregion //#region native methods -/** - * Returns a string containing a number represented in exponential notation. - * @param n - * @returns - */ -export const toExponential = (n: number) => n.toExponential(0); /** * Returns a string containing a number represented in exponential notation. * - * ```typescript - * import { Number } from 'tiinvo'; + * If `a` and `b` parameters are passed, `b` counts as the fraction digits for `a`. * - * console.log(Number.toExponentialF(10)(1)) // '1.0000000000e+1' - * ``` + * If `b` parameter is not passed, returns a `Unary` function and `a` counts as the fraction digits for `b`. * - * @param fractionDigits - * @returns - */ -export const toExponentialF = (fractionDigits: number) => (n: number) => n.toExponential(fractionDigits); - -/** - * Returns a string representing a number in fixed-point notation. + * @example * - * ```typescript - * import { Number } from 'tiinvo'; + * ```ts + * import { Num } from 'tiinvo'; * - * console.log(Number.toFixed(1)) // '1.00' + * Num.toExponential(10, 2) // "1.00e+1" + * Num.toExponential(10)(2) // "2.0000000000e+0" * ``` * - * @param n - * @returns + * @since 4.0.0 */ -export const toFixed = (n: number) => n.toFixed(2); +export function toExponential(a: t, b: t): string; +export function toExponential(a: t): Fn.Unary; +export function toExponential(a: t, b?: any): any { + if (guard(b)) { + return a.toExponential(b); + } + + return (b: t) => b.toExponential(a); +} + /** * Returns a string representing a number in fixed-point notation. * - * ```typescript - * import { Number } from 'tiinvo'; + * If `a` and `b` parameters are passed, `b` counts as the fraction digits for `a`. * - * console.log(Number.toFixed(10)(1)) // '1.0000000000' - * ``` - * @param fractionDigits - * @returns - */ -export const toFixedF = (fractionDigits: number) => (n: number) => n.toFixed(fractionDigits); -/** - * Returns a string containing a number represented either in exponential or fixed-point notation with a specified number of digits. - * - * ```typescript - * import { Number } from 'tiinvo'; + * If `b` parameter is not passed, returns a `Unary` function and `a` counts as the fraction digits for `b`. * - * console.log(Number.toPrecision(1)) // '1.0' - * ``` - * @param n - * @returns - */ -export const toPrecision = (n: number) => n.toPrecision(2); -/** - * Returns a string containing a number represented either in exponential or fixed-point notation with a specified number of digits. + * @example * - * ```typescript - * import { Number } from 'tiinvo'; + * ```ts + * import { Num } from 'tiinvo'; * - * console.log(Number.toPrecision(10)(1)) // '1.0000000000' + * Num.toFixed(10.505, 2) // "10.51" + * Num.toFixed(10.505)(2) // "2.0000000000" * ``` - * @param precision - * @returns + * + * @since 4.0.0 */ -export const toPrecisionP = (precision: number) => (n: number) => n.toPrecision(precision); +export function toFixed(a: t, b: t): string; +export function toFixed(a: t): Fn.Unary; +export function toFixed(a: t, b?: any): any { + if (guard(b)) { + return a.toFixed(b); + } + + return (b: t) => b.toFixed(a); +} + /** - * Returns a string representation of a number. + * Returns a string representing a number in fixed-point notation. * - * ```typescript - * import { Number } from 'tiinvo'; + * If `a` and `b` parameters are passed, `b` counts as the fraction digits for `a`. * - * console.log(Number.toString()) // '1' - * ``` - * @param n - * @returns - */ -export const toString = (n: number) => n.toString(); -/** - * Returns a string representation of a number. + * If `b` parameter is not passed, returns a `Unary` function and `a` counts as the fraction digits for `b`. * - * ```typescript - * import { Number } from 'tiinvo'; + * @example * - * console.log(Number.toString(16)(4bc)) // '4bc' + * ```ts + * import { Num } from 'tiinvo'; + * + * Num.toPrecision(10, 2) // "10" + * Num.toPrecision(10)(2) // "2.000000000" * ``` - * @param radix - * @returns + * + * @since 4.0.0 */ -export const toStringR = (radix: number) => (n: number) => n.toString(radix); +export function toPrecision(a: t, b: t): string; +export function toPrecision(a: t): Fn.Unary; +export function toPrecision(a: t, b?: any): any { + if (guard(b)) { + return a.toPrecision(b); + } + return (b: t) => b.toPrecision(a); +} //#endregion -//#region unary +//#region operables -export type unaryop = fn.unary>; /** - * Adds b to a + * Adds `a` to `b` if both specified, otherwise returns a `Unary` + * function which once called adds `b` to `a` * - * ```typescript - * import { Number } from 'tiinvo'; + * @example * - * console.log(Number.uadd(10)(5)) // 15 - * ``` - * @param a - * @returns - */ -export const uadd: unaryop = a => b => a + b; -/** - * Divides b by a + * ```ts + * import { Num } from 'tiinvo'; * - * ```typescript - * import { Number } from 'tiinvo'; + * Num.add(5, -2) // 3 + * Num.add(5, 12) // 17 * - * console.log(Number.udiv(10)(5)) // 2 + * const add5 = Num.add(5) + * + * add5(10) // 15 * ``` - * @param a - * @returns */ -export const udiv: unaryop = a => b => b / a; +export function add(a: t, b: t): t; +export function add(a: t): Fn.Unary; +export function add(a: t, b?: t): any { + if (guard(b)) { + return a + b; + } + + return (c: t) => c + a; +} + /** - * Returns the modulus of b by a + * Divides `a` by `b` if both specified, otherwise returns a `Unary` + * function which once called divides `b` by `a` + * + * @example + * + * ```ts + * import { Num } from 'tiinvo'; * - * ```typescript - * import { Number } from 'tiinvo'; + * Num.div(4, 2) // 2 + * Num.div(12, 3) // 4 * - * console.log(Number.umod(10)(5)) // 5 + * const div2 = Num.div(2) + * + * div2(4) // 2 * ``` - * @param a - * @returns */ -export const umod: unaryop = a => b => b % a; +export function div(a: t, b: t): t; +export function div(a: t): Fn.Unary; +export function div(a: t, b?: t): any { + if (guard(b)) { + return a / b; + } + + return (c: t) => c / a; +} + /** - * Multiplies b by a + * Returns the modulus of `a % b` if `b` parameter is passed, + * otherwise returns a `Unary` + * function which once called returns the modulus of `b % a` + * + * @example + * + * ```ts + * import { Num } from 'tiinvo'; + * + * Num.mod(2, 2) // 0 + * Num.mod(3, 2) // 1 * - * ```typescript - * import { Number } from 'tiinvo'; + * const mod2 = Num.mod(2) * - * console.log(Number.umul(10)(5)) // 50 + * mod2(10) // 0 + * mod2(15) // 1 * ``` - * @param a - * @returns */ -export const umul: unaryop = a => b => a * b; +export function mod(a: t, b: t): t; +export function mod(a: t): Fn.Unary; +export function mod(a: t, b?: t): any { + if (guard(b)) { + return a % b; + } + + return (c: t) => c % a; +} + /** - * Exponentiates b by a + * Multiplies `a` to `b` if both specified, otherwise returns a `Unary` + * function which once called multiplies `b` to `a` + * + * @example + * + * ```ts + * import { Num } from 'tiinvo'; + * + * Num.mul(5, -2) // -10 + * Num.mul(5, 12) // 60 * - * ```typescript - * import { Number } from 'tiinvo'; + * const mul5 = Num.mul(5) * - * console.log(Number.upow(3)(5)) // 125 + * mul5(10) // 50 * ``` - * @param a - * @returns */ -export const upow: unaryop = a => b => Math.pow(b, a); +export function mul(a: t, b: t): t; +export function mul(a: t): Fn.Unary; +export function mul(a: t, b?: t): any { + if (guard(b)) { + return a * b; + } + + return (c: t) => c * a; +} + /** - * Returns a random number between b and a (inclusive) + * Elevates `a` by `b` if both specified, otherwise returns a `Unary` + * function which once called elevates `b` by `a` + * + * @example + * + * ```ts + * import { Num } from 'tiinvo'; + * + * Num.pow(2, 3) // 8 + * Num.pow(3, 2) // 9 * - * ```typescript - * import { Number } from 'tiinvo'; + * const pow5 = Num.pow(5) * - * console.log(Number.urand(10)(5)) // 5 + * pow5(10) // 100_000 * ``` - * @param a - * @returns */ -export const urand: unaryop = a => b => Math.random() * (a - b) + b; +export function pow(a: t, b: t): t; +export function pow(a: t): Fn.Unary; +export function pow(a: t, b?: t): any { + if (guard(b)) { + return a ** b; + } + + return (c: t) => c ** a; +} + /** - * Returns the root of b by a + * Square root of `a` under `b` if both specified, otherwise returns a `Unary` + * function which once called returns the root of `b` under `a` + * + * @example + * + * ```ts + * import { Num } from 'tiinvo'; + * + * Num.root(4, 2) // 2 + * Num.root(9, 2) // 3 * - * ```typescript - * import { Number } from 'tiinvo'; + * const root2 = Num.root(2) * - * console.log(Number.uroot(2)(25)) // 5 + * root2(4) // 2 + * root2(9) // 3 * ``` - * @param a - * @returns */ -export const uroot: unaryop = a => b => Math.pow(b, 1 / a); +export function root(a: t, b: t): t; +export function root(a: t): Fn.Unary; +export function root(a: t, b?: t): any { + if (guard(b)) { + return a ** (1 / b); + } + + return (c: t) => c ** (1 / a); +} + /** - * Subtracts a from b + * Subtracts `b` to `a` if both specified, otherwise returns a `Unary` + * function which once called subtracts `a` to `b` + * + * @example + * + * ```ts + * import { Num } from 'tiinvo'; * - * ```typescript - * import { Number } from 'tiinvo'; + * Num.sub(5, -2) // 7 + * Num.sub(5, 12) // -7 * - * console.log(Number.usub(10)(5)) // -5 + * const sub5 = Num.sub(5) + * + * sub5(10) // 5 + * sub5(-2) // -7 * ``` - * @param a - * @returns */ -export const usub: unaryop = a => b => b - a; +export function sub(a: t, b: t): t; +export function sub(a: t): Fn.Unary; +export function sub(a: t, b?: t): any { + if (guard(b)) { + return a - b; + } + return (c: t) => c - a; +} //#endregion -//#region binary +//#region sortables -export type binaryop = fn.binary /** - * Adds a and b + * Compares two numbers `a` and `b` if `b` is defined, otherwise returns a + * `Unary` function which once called compares `b` and `a` * - * ```typescript - * import { Number } from 'tiinvo'; + * Great to sort a numeric array in ASC direction. * - * console.log(Number.badd(10, 5)) // 15 - * ``` - * @param a - * @param b - * @returns - */ -export const badd: binaryop = (a, b) => a + b; -/** - * Divides a by b + * @example * - * ```typescript - * import { Number } from 'tiinvo'; + * ```ts + * import { Num } from 'tiinvo'; * - * console.log(Number.bdiv(10, 5)) // 2 + * const collection = [10, 5, 6, 4, 12, 22, 3]; + * + * collection.sort(Num.asc) // [3, 4, 5, 6, 10, 12, 22] * ``` - * @param a - * @param b - * @returns + * + * @since 4.0.0 */ -export const bdiv: binaryop = (a, b) => a / b; +export function asc(a: t, b: t): Functors.ComparableResult; +export function asc(a: t): Fn.Unary; +export function asc(a: t, b?: any): any { + if (guard(b)) { + return cmp(a, b); + } + + return (c: t) => cmp(c, a); +} + /** - * Returns the modulus of a by b + * Compares two numbers `b` and `a` if `b` is defined, otherwise returns a + * `Unary` function which once called compares `a` and `b` + * + * Great to sort a numeric array in DESC direction. * - * ```typescript - * import { Number } from 'tiinvo'; + * @example * - * console.log(Number.bmod(10, 5)) // 0 + * ```ts + * import { Num } from 'tiinvo'; + * + * const collection = [10, 5, 6, 4, 12, 22, 3]; + * + * collection.sort(Num.desc) // [22, 12, 10, 6, 5, 4, 3] * ``` - * @param a - * @param b - * @returns + * + * @since 4.0.0 */ -export const bmod: binaryop = (a, b) => a % b; +export function desc(a: t, b: t): Functors.ComparableResult; +export function desc(a: t): Fn.Unary; +export function desc(a: t, b?: any): any { + if (guard(b)) { + return cmp(b, a); + } + + return (c: t) => cmp(a, c); +} + +//#endregion + +//#region serializables + /** - * Multiplies a by b + * Returns a number in binary notation. + * + * If the passed argument at runtime is not a number, an Error will be returned. * - * ```typescript - * import { Number } from 'tiinvo'; + * @example * - * console.log(Number.bmul(10, 5)) // 50 + * ```ts + * import { Num } from 'tiinvo'; + * + * Num.toBin(10) // "0b1010" * ``` - * @param a - * @param b - * @returns + * + * @since 4.0.0 */ -export const bmul: binaryop = (a, b) => a * b; +export const toBin = map(x => '0b' + x.toString(2)); + /** - * Elevates a to the power of b + * Returns a number in hexadecimal notation + * + * If the passed argument at runtime is not a number, an Error will be returned. + * + * @example * - * ```typescript - * import { Number } from 'tiinvo'; + * ```ts + * import { Num } from 'tiinvo'; * - * console.log(Number.bpow(3, 5)) // 125 + * Num.toHex(10) // "0xa" * ``` - * @param a - * @param b - * @returns + * + * @since 4.0.0 */ -export const bpow: binaryop = (a, b) => Math.pow(a, b); +export const toHex = map(x => '0x' + x.toString(16)); + /** - * Returns a random number between a and b (inclusive) + * Returns a number in octal notation + * + * If the passed argument at runtime is not a number, an Error will be returned. + * + * @example * - * ```typescript - * import { Number } from 'tiinvo'; + * ```ts + * import { Num } from 'tiinvo'; * - * console.log(Number.brand(10, 5)) // 5 + * Num.toOct(10) // "0o12" * ``` - * @param a - * @param b - * @returns + * + * @since 4.0.0 */ -export const brand: binaryop = (a, b) => Math.random() * (b - a) + a; +export const toOct = map(x => '0o' + x.toString(8)); + /** - * Returns the root of a by b + * Returns a number in json notation. + * + * If the passed argument at runtime is not a number, an Error will be returned. * - * ```typescript - * import { Number } from 'tiinvo'; + * @example * - * console.log(Number.broot(25, 2)) // 5 + * ```ts + * import { Num } from 'tiinvo'; + * + * Num.toJSON(10) // "10" * ``` - * @param a - * @param b - * @returns + * + * @since 4.0.0 */ -export const broot: binaryop = (a, b) => Math.pow(a, 1 / b); +export const toJSON = map(JSON.stringify); + /** - * Subtracts b from a + * Returns a stringified number. + * + * If the passed argument at runtime is not a number, an Error will be returned. * - * ```typescript - * import { Number } from 'tiinvo'; + * @example * - * console.log(Number.bsub(10, 5)) // 5 + * ```ts + * import { Num } from 'tiinvo'; + * + * Num.toString(10) // "10" * ``` - * @param a - * @param b - * @returns + * + * @since 4.0.0 */ -export const bsub: binaryop = (a, b) => a - b; +export const toString = map(String); //#endregion diff --git a/src/obj.ts b/src/obj.ts index f11ba76..df311d0 100644 --- a/src/obj.ts +++ b/src/obj.ts @@ -1,483 +1,388 @@ -import type * as f from './functors'; -import type * as o from './option'; - -export type flat = object extends a - ? object - : { - [k in keyof a]-?: (x: NonNullable extends infer b - ? b extends object - ? - b extends readonly any[] - ? Pick - : flat extends infer c - ? ({ - [kc in keyof c as `${Extract}.${Extract}`]: c[kc] - }) - : never - : Pick - : never - ) => void - } extends Record void> - ? d extends infer _U - ? { [kd in keyof d]: d[kd] } - : never - : never - -export type entries = { +import type * as Functors from './Functors.js'; +import { Fn } from './index.js'; +import type * as Option from './Option.js'; + +export type t = object; + +/** + * Represents the entries of an object `a` as an array of key/value pair tuples + */ +export type Entries = { [k in keyof a]: [k, a[k]]; }[keyof a][]; -export type keys = (keyof a)[]; - -export type guardsFromStruct = { - [key in keyof T]: T[key] extends o.option ? f.guard> | guardsFromStruct : f.guard | guardsFromStruct; +export type GuardsFromStruct = { + [key in keyof a]: a[key] extends Option.t ? Functors.Guardable> | GuardsFromStruct : Functors.Guardable | GuardsFromStruct; }; -export type values = (a[keyof a])[]; +/** + * Extracts keys `k` from object `a` + */ +export type KeysOf = (keyof a)[]; /** - * Copy the values of all of the enumerable own properties from one or more source objects to a target object. Returns the target object. - * - * ```ts - * import { Object } from 'tiinvo'; + * Extracts values `x` from object `a` + */ +export type ValuesOf = (a[keyof a])[]; + +//#region guards + +/** + * Returns true if a is a `object` and not null * - * const a = { a: 1, b: 2 }; - * const b = { b: 3, c: 4 }; - * const c = { c: 5, d: 6 }; + * ```typescript + * import { Obj } from 'tiinvo'; * - * Obj.assign(a, b, c); - * // { a: 1, b: 3, c: 5, d: 6 } + * Obj.guard({}); // true + * Obj.guard(null); // false * ``` * - * @param target The target object to copy to. - * @param sources One or more source objects from which to copy properties - * @returns The target object - * - * @since 3.0.9 + * @param a + * @returns + * @since 4.0.0 */ -export const assign = Object.assign; +export const guard: Functors.Guardable = (x): x is object => typeof (x) === 'object' && !!x; /** - * Compares two objects deeply. + * Returns `true` if a value `v` implements a shape `s` * * ```typescript - * import { Object } from 'tiinvo'; + * import { Obj, Str, Num, Bool } from 'tiinvo'; * - * const a = { a: 1, b: { c: 2 } }; - * const b = { a: 1, b: { c: 2 } }; - * const c = { a: 1, b: { c: 3 } }; + * const guardStruct = Obj.guardOf({ + * a: Str.guard, + * b: Num.guard, + * c: Bool.guard + * }); * - * Obj.cmp(a, b); // 0 - * Obj.cmp(a, c); // -1 - * Obj.cmp(c, a); // 1 + * guardStruct({ a: `foo`, b: 1, c: true }); // true + * guardStruct({ a: `foo`, b: false, c: 1 }); // false * ``` * - * @param a - * @param b + * @param s * @returns - * @since 3.0.0 + * @since 4.0.0 */ -export const cmp: f.comparableE, Record> = (a, b) => { - const aflat = flat(a) as Record; - const bflat = flat(b) as Record; - const allkeys = Array.from(new Set([...keys(aflat), ...keys(bflat)])); - const results: (-1 | 0 | 1)[] = []; - - for (let i = 0; i < allkeys.length; i++) { - const key = allkeys[i]; - const has = haskey(key); - - if (has(aflat) && has(bflat)) { - const va: any = aflat[key]; - const vb: any = bflat[key]; - - results.push(va === vb ? 0 : va < vb ? -1 : 1); - } else if (has(aflat) && !has(bflat)) { - results.push(-1); - } else if (!has(aflat) && has(bflat)) { - results.push(1); - } +export const guardOf = (s: GuardsFromStruct) => { + if (!guard(s)) { + throw new TypeError("Invalid Struct guard, expected an object (GuardsFromStruct), got " + typeof s); } - const result = results.reduce((acc, cur) => acc + cur, 0 as any); + return (v: unknown): v is a => { + + if (!guard(v)) { + return false; + } + + const keys = Object.keys(s); + + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const currentvalue = v[key as keyof typeof v]; + const currentguard = s[key as keyof typeof s]; + const case1 = guard(currentguard) && guardOf(currentguard as any)(currentvalue); + const case2 = typeof currentguard === 'function' && currentguard(currentvalue); + + if (!case1 && !case2) { + return false; + } + } + + return true; + }; +}; - return result === 0 ? 0 : result > 0 ? 1 : -1; -} /** - * Adds a property to an object, or modifies attributes of an existing property. + * Returns true if a is a `object` and has property `k` * * ```typescript - * import { Object } from 'tiinvo'; + * import { Obj } from 'tiinvo'; * - * const a = { a: 1, b: 2 }; - * Obj.defineProperty(a, 'c', { value: 3, enumerable: true, configurable: true, writable: true }); - * // { a: 1, b: 2, c: 3 } + * const hasa = Obj.hasKey('a'); + * hasa({}); // false + * hasa({ a: 1 }); // true * ``` * - * @since 3.1.0 + * @param k + * @returns + * @since 4.0.0 */ -export const defineProperty = Object.defineProperty +export const hasKey = (k: k) => (o: unknown): o is Record => guard(o) && o.hasOwnProperty(k); /** - * Returns object entries + * Returns true if a is a `object` and has property `k` of type `g` * * ```typescript - * import { Object } from 'tiinvo'; + * import { Obj, Num } from 'tiinvo'; * - * const entries = Object.entries({ a: 1, b: 2 }); - * console.log(entries); // [ ['a', 1], ['b', 2] ] + * const hasa = Obj.hasKeyOf('a', Num.guard); + * + * hasa({}) // false + * hasa({ a: 1 }) // true + * hasa({ a: `nope` }) // false * ``` - * @param o + * + * @param k + * @param g * @returns - * @since 3.0.0 + * @since 4.0.0 + */ +export function hasKeyOf(k: k, g: Functors.Guardable, o: unknown): o is Record; +export function hasKeyOf(k: k, g: Functors.Guardable): (o: unknown) => o is Record; +export function hasKeyOf(k: k | Functors.Guardable, g?: any, o?: any): any { + const og = (k: k, g: Functors.Guardable, o: unknown): o is Record => + guard(o) && o.hasOwnProperty(k) && g((o as any)[k]); + + if (!!k && !!g && !!o) { + return og(k as k, g, o); + } + + return (o: unknown) => og(k as any, g, o); +} + +//#endregion + +//#region native methods + +/** + * Copy the values of all of the enumerable own properties from one or more source objects to a target object. Returns the target object. + * + * ```ts + * import { Obj } from 'tiinvo'; + * + * const a = { a: 1, b: 2 }; + * const b = { b: 3, c: 4 }; + * const c = { c: 5, d: 6 }; + * + * Obj.assign(a, b, c); + * // { a: 1, b: 3, c: 5, d: 6 } + * ``` + * + * @param target The target object to copy to. + * @param sources One or more source objects from which to copy properties + * @returns The target object + * + * @since 4.0.0 */ -export const entries = (o: a): entries => Object.entries(o) as entries; +export const assign = Object.assign; + /** - * Returns a flat representation for `a`. + * Returns object entries * - * Ideal to use it for diff or for mongodb queries/inserts. + * @example * * ```ts - * import { Object } from 'tiinvo'; - * - * const myobject = { - * a: { - * b: { - * c: 100 - * } - * }, - * d: 20 - * } - * - * Obj.flat(myobject) // { 'a.b.c': 100, d: 20 } + * import { Obj } from 'tiinvo'; + * + * Obj.entries({ a: 1, b: 2 }); // [ ['a', 1], ['b', 2] ] * ``` * - * @param obj - * @param prefix * @returns - * @since 3.0.0 + * @since 4.0.0 */ -export const flat = >(obj: a, prefix = ``): flat => { - const flattened = {} as any - const kl = keys(obj) - - for (let i = 0; i < kl.length; i++) { - const key = kl[i] as string; - const prefixed = prefix ? `${prefix}.${key}` : key; - const value = (obj as any)[key] - - if (typeof value === 'object' && value !== null) { - Object.assign(flattened, flat(value, prefixed)) - } else { - flattened[prefixed] = value - } - } +export const entries = Object.entries as (a: a) => Entries; - return flattened -} /** * Prevents the modification of existing property attributes and values, and prevents the addition of new properties. * + * @example + * * ```ts - * import { Object } from 'tiinvo'; + * import { Obj } from 'tiinvo'; * * const a = { a: 1, b: 2 }; * Obj.freeze(a); * a.a = 100; // throws * ``` * - * @since 3.1.0 + * @since 4.0.0 */ export const freeze = Object.freeze; + /** * Returns an object created by key-value entries for properties and methods * - * ```typescript - * import { Object } from 'tiinvo'; + * @example * - * const fromEntries = Object.fromEntries([ - * ['a', 1], - * ['b', 2] - * ]); + * ```ts + * import { Obj } from 'tiinvo'; * - * console.log(fromEntries); // { a: 1, b: 2 } + * Obj.fromEntries([ ['a', 1], ['b', 2] ]) // { a: 1, b: 2 } * ``` * * @param entries * @returns - * @since 3.1.0 + * @since 4.0.0 */ export const fromEntries = Object.fromEntries; + /** - * Gets a property `a` from an object `b` and returns a `option` - * - * ```typescript - * import { Object } from 'tiinvo'; + * Gets a property `a` from an object `b` and returns a `Option.t` + * + * @example + * + * ```ts + * import { Obj } from 'tiinvo'; * - * const get = Object.get(`foo`); + * const get = Obj.get(`foo`); * - * console.log(get({ foo: `bar` })); // some<`bar`> - * console.log(get({})); // none + * get({ foo: `bar` }); // some<`bar`> + * get({}); // none * ``` * * @param a the property to get * @param b the object to get the property from * @returns - * @since 3.0.0 + * @since 4.0.0 */ -export const get = (a: a) => (b: b): o.option ? u : null> => haskey(a)(b) ? b[a] : null as o.option; +export const get = (a: a) => (b: b): Option.t ? u : null> => hasKey(a)(b) ? b[a] : null as Option.t; + /** - * Returns true if a is a `object` and not null - * - * ```typescript - * import { Object } from 'tiinvo'; - * - * console.log(Object.isobject({})); // true - * console.log(Object.isobject(null)); // false - * ``` + * Omits the keys in `a` that are in `b` * - * @param a - * @returns - * @since 3.0.0 - */ -export const guard = (a => typeof a === 'object' && a !== null) as f.guard -/** - * Returns `true` if a value `v` implements a shape `s` + * @example * * ```typescript - * import { Obj, String, Number, Boolean } from 'tiinvo'; - * - * const isshape = Object.guardOf({ - * a: String.guard, - * b: Number.guard, - * c: Boolean.guard - * }); + * import { Obj } from 'tiinvo'; * - * console.log(isshape({ a: `foo`, b: 1, c: true })); // true - * console.log(isshape({ a: `foo`, b: false, c: 1 })); // false + * const omit = Obj.omit(['a', 'b']) + * omit({ a: 1, b: 2, c: 3 }) // { c: 3 } + * omit({ a: 1, b: 2 }) // {} * ``` * - * @param s + * @param k * @returns - * @since 3.0.0 + * @since 4.0.0 */ -export const guardOf = ( - s: guardsFromStruct -) => (v: unknown): v is a => { - if (!guard(s) || !guard(v)) { - return false; - } - - const keys = Object.keys(s); +export function omit>(k: a[], b: b): Exclude; +export function omit>(k: a[]): Fn.Unary>; +export function omit>(k: any, b?: any): any { + const fn = (k: a[], b: b) => { + const omitted = {} as Exclude; + const ownedkeys = keys(b); - for (let i = 0; i < keys.length; i++) { - const key = keys[i]; - const currentvalue = v[key as keyof typeof v]; - const currenttg = s[key as keyof typeof s]; - const case1 = guard(currenttg) && guardOf(currenttg as any)(currentvalue); - const case2 = typeof currenttg === 'function' && currenttg(currentvalue); + for (let index = 0; index < ownedkeys.length; index++) { + const key = ownedkeys[index]; - if (!case1 && !case2) { - return false; + if (!k.includes(key as a)) { + (omitted as any)[key] = b[key]; + } } + + return omitted; + }; + + if (Array.isArray(k) && !!b) { + return fn(k, b); } - return true; + return (b: b) => fn(k, b); }; /** - * Returns true if a is a `object` and has property `k` - * - * ```typescript - * import { Object } from 'tiinvo'; - * - * const hasa = Object.haskey('a'); - * console.log(hasa({})); // false - * console.log(hasa({ a: 1 })); // true - * ``` - * - * @param k - * @returns - * @since 3.0.0 - */ -export const haskey = (k: k) => (o: unknown): o is Record => guard(o) && o.hasOwnProperty(k); -/** - * Returns true if a is a `object` and has property `k` of type `g` - * - * ```typescript - * import { Object } from 'tiinvo'; - * import * as num from 'tiinvo/num'; - * - * const hasa = Object.haskeyOf('a', num.guard); - * - * console.log(hasa({})); // false - * console.log(hasa({ a: 1 })); // true - * console.log(hasa({ a: `nope` })); // false - * ``` - * - * @param k - * @param g - * @returns - * @since 3.0.0 - */ -export const haskeyOf = (k: k, g: f.guard) => (o: unknown): o is Record => guard(o) && o.hasOwnProperty(k) && g((o as any)[k]); -/** - * Returns the names of the enumerable string properties and methods of an object. + * Returns a new object with the keys of `a` that are in `b` * * ```typescript - * import { Object } from 'tiinvo'; - * - * console.log(Object.keys({ a: 1, b: 2 })); // ['a', 'b'] - * console.log(Object.keys({})); // [] - * ``` - * - * @param a - * @returns - * @since 3.0.0 - */ -export const keys = (a: a): keys => Object.keys(a) as keys; -/** - * Maps a function over the entries of an object. + * import { Obj } from 'tiinvo'; * - * ```ts - * import { Object, Number } from 'tiinvo'; - * - * const map = Object.map(Number.umul(2)); - * const map2 = obj.map((a: number | string | boolean) => { - * switch (typeof a) { - * case 'number': return a * 2; - * case 'string': return a + a; - * case 'boolean': return !a; - * } - * }) - * console.log(map({ a: 1, b: 2 })); // { a: 2, b: 4 } - * console.log(map2({ a: 2, b: 'hello', c: true })); // { a: 4, b: 'hellohello', c: false } + * const pick = Obj.pick(['a', 'b']); + * pick({ a: 1, b: 2, c: 3 }); // { a: 1, b: 2 } + * pick({ a: 1, b: 2 }); // { a: 1, b: 2 } * ``` * - * @param fn + * @param keys * @returns - * @since 3.1.0 + * @since 4.0.0 */ -export const map = (fn: (b: b, a: a, i: number) => c) => (d: d): { [key in a]: c } => { - const result = {} as { [key in a]: c }; - const kl = keys(d); +export function pick>(keys: a[], b: b): Pick; +export function pick>(keys: a[]): Fn.Unary>; +export function pick>(keys: any, b?: any): any { + const fn = (keys: a[], b: b): Pick => { + const o = {} as Pick; - for (let i = 0; i < kl.length; i++) { - const v = d[kl[i]]; - result[kl[i] as keyof typeof result] = fn(v, kl[i] as a, i) as c; - } + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; - return result; -} -/** - * Maps a function over the keys of an object. - * - * ```ts - * import { Object, String } from 'tiinvo'; - * - * const map = Object.mapkeys(String.upper); - * console.log(map({ a: 1, b: 2 })); // { A: 1, B: 2 } - * ``` - * - * @param fn - * @returns - * - * @since 3.1.0 - */ -export const mapkeys = (fn: (a: a, i: number) => b) => (c: c): { [key in b]: d } => { - const result = {} as { [key in b]: d }; - const kl = keys(c); + if (hasKey(key)(b)) { + o[key] = b[key]; + } + } + + return o; + }; - for (let i = 0; i < kl.length; i++) { - const v = c[kl[i]]; - result[fn(kl[i] as a, i)] = v; + if (Array.isArray(keys) && !!b) { + return fn(keys, b); } - return result; + return (b: b) => fn(keys, b); } + +//#endregion + +//#region mappables + /** - * Omits the keys in `a` that are in `b` + * Maps an object `a` to a value `b` * * ```typescript - * import { Object } from 'tiinvo'; + * import { Obj } from 'tiinvo'; * - * const omit = Object.omit(['a', 'b']); - * console.log(omit({ a: 1, b: 2, c: 3 })); // { c: 3 } - * console.log(omit({ a: 1, b: 2 })); // {} + * Obj.map(Object.keys)({ a: 10, b: 20 }); // ['a', 'b'] + * Obj.map((x: Record) => x.a ?? 0)({ a: 10 }); // 10 + * Obj.map((x: Record) => x.a ?? 0)({ b: 10 }); // 0 * ``` - * @param k - * @returns - * @since 3.0.0 - */ -export const omit = (k: a[]) => >(b: b) => { - const omitted = {} as Exclude; - const ownedkeys = keys(b); - - for (let index = 0; index < ownedkeys.length; index++) { - const key = ownedkeys[index]; - - if (!k.includes(key as a)) { - (omitted as any)[key] = b[key]; - } - } + * + * @param a + * @returns + * @since 4.0.0 + **/ +export const map = (m: Functors.Mappable) => (a: a): Option.t => guard(a) ? m(a) as Option.t : null; - return omitted; -} /** - * Returns a new object with the keys of `a` that are in `b` + * Returns the names of the enumerable string properties and methods of an object. * - * ```typescript - * import { Object } from 'tiinvo'; + * @example + * ```ts + * import { Obj } from 'tiinvo'; * - * const pick = Object.pick(['a', 'b']); - * console.log(pick({ a: 1, b: 2, c: 3 })); // { a: 1, b: 2 } - * console.log(pick({ a: 1, b: 2 })); // { a: 1, b: 2 } + * Obj.keys({ a: 10, b: 20 }) // ['a', 'b'] * ``` * - * @param keys - * @returns - * @since 3.0.0 + * @since 4.0.0 */ -export const pick = (keys: a[]) => >(b: b) => { - const o = {} as Pick; +export const keys = Object.keys as (x: x) => KeysOf; - for (let i = 0; i < keys.length; i++) { - const key = keys[i]; - - if (haskey(key)(b)) { - o[key] = b[key]; - } - } - - return o; -} /** * Returns an object size * * ```typescript - * import { Object } from 'tiinvo'; + * import { Obj } from 'tiinvo'; * - * console.log(Object.size({ a: 1, b: 2 })); // 2 - * console.log(Object.size({})); // 0 + * Obj.size({ a: 1, b: 2 }) // 2 + * Obj.size({}) // 0 * ``` * * @param a * @returns - * @since 3.0.10 + * @since 4.0.0 **/ -export const size = >(a: a): number => Object.keys(a).length; +export const size = (x: object) => keys(x).length; + /** * Returns an array of values of the enumerable properties of an object * * ```typescript - * import { Object } from 'tiinvo'; + * import { Obj } from 'tiinvo'; * - * console.log(Object.values({ a: 1, b: 2 })); // [1, 2] - * console.log(Object.values({})); // [] + * Obj.values({ a: 1, b: 2 }) // [1, 2] + * Obj.values({}) // [] * ``` * - * @param a - * @returns - * @since 3.0.0 - */ -export const values = >(a: a): values => Object.values(a) as values; \ No newline at end of file + * @param a + * @returns + * @since 4.0.0 + **/ +export const values = Object.values as (o: a) => ValuesOf;; + +//#endregion diff --git a/src/option.ts b/src/option.ts index bf4cb7c..1b418a4 100644 --- a/src/option.ts +++ b/src/option.ts @@ -1,264 +1,297 @@ -import type * as f from './functors'; +import type * as Functors from './Functors.js'; +import { catchableAsync, catchableSync } from './Functors.js'; +import type * as Fn from './Fn.js'; +import { make as makecatch } from './Catch.js'; -export type some = a extends null | undefined ? none : a; /** - * none represents a value that is not present. + * None represents a value that is not present. * Unfortunately in javascript, null and undefined are not the same thing, * but we can use them interchangeably within the option type. - * @since 3.0.0 + * @since 4.0.0 */ -export type none = null | undefined; +export type None = null | undefined; + +export type Some = a extends None ? never : a; /** - * The type `option` represents a value that could be both `a` or `null` or `undefined`. - * @since 3.0.0 + * The type `Option.t` represents a value that could be both `a` or `null` or `undefined`. + * @since 4.0.0 */ -export type option = some | none; +export type t = Some | None; /** - * Returns `true` if the option is `none`, `false` otherwise. + * Returns `true` if the option is `None`, `false` otherwise. * * ```typescript * import { Option } from 'tiinvo'; * - * Option.isNone(1); // false - * Option.isNone(null); // true - * Option.isNone(undefined); // true + * Option.isNone(1); // false + * Option.isNone(null); // true + * Option.isNone(undefined); // true * ``` * @param value * @returns - * @since 3.0.0 + * @since 4.0.0 */ -export const isNone = (value: unknown): value is none => value === null || value === undefined; +export const isNone: Functors.Guardable = (x): x is None => x === undefined || x === null; /** - * Returns `true` if the option is `some`, `false` otherwise. + * Returns `true` if the option is `Some`, `false` otherwise. * * ```typescript * import { Option } from 'tiinvo'; * - * const x = 1 - * const y = null - * const z = undefined - * - * Option.isSome(1); // true - * Option.isSome(null); // false - * Option.isSome(undefined); // false + * Option.isSome(1) // true + * Option.isSome(null) // false + * Option.isSome(undefined) // false * ``` * * @param value * @returns - * @since 3.0.0 + * @since 4.0.0 */ -export const isSome = (value: unknown): value is some => value !== null && value !== undefined; +export const isSome: Functors.Guardable> = (x): x is Some => x !== undefined && x !== null; /** - * Returns `true` if the option is `some` and the value type is satisfied by the guard, otherwise `false`. + * Returns `true` if the option is `Some` and the value type is satisfied by the guard, otherwise `false`. * - * If the option is `none`, it will always return `true`. + * If the option is `None`, it will always return `true`. * * ```typescript - * import { Option } from 'tiinvo'; - * import * as num from 'tiinvo/num'; + * import { Num, Option } from 'tiinvo'; * * const x = 1 * const y = null * const z = undefined * const w = `a` * - * const isnumsome = Option.isOptionOf(num.guard); + * const isnumsome = Option.guardOf(Num.guard); * - * isnumsome(x); // true - * isnumsome(y); // true - * isnumsome(z); // true - * isnumsome(w); // false + * isnumsome(x) // true + * isnumsome(y) // true + * isnumsome(z) // true + * isnumsome(w) // false * * ``` * @param guard * @returns - * @since 3.0.0 + * @since 4.0.0 */ -export const isOptionOf = (guard: f.guard) => (value: unknown): value is option => isSome(value) ? guard(value) : true; +export const guardOf = (f: Functors.Guardable): Functors.Guardable> => (x): x is t => isNone(x) ? true : f(x); + +//#region comparables + /** - * Compares two options for equality. + * Compares two optinos `t` by a given `Comparable`. * - * ```typescript - * import { Option } from 'tiinvo'; + * Returns -1 if `a` is less than `b`, 0 if `a` is same of `b` and 1 if `a` is greater than `b`. * - * const x = 1 - * const y = null - * const z = undefined + * If `a` is `None` and `b` is `Some` returns -1, else if both are `None` returns 0, else returns 1 + * + * @example + * + * ```ts + * import { Str, Option } from 'tiinvo'; + * + * const cmp = Option.cmp(Str.cmp); * - * Option.cmp(x, y); // 1 - * Option.cmp(x, z); // 1 - * Option.cmp(y, z); // 0 - * Option.cmp(x, x); // 0 + * cmp("a", "a") // 0 + * cmp("a", "b") // -1 + * cmp("b", "a") // 1 + * cmp(null, undefined) // 0 + * cmp(null, "a") // -1 + * cmp("a", undefined) // 1 * ``` - * @param a - * @param b - * @returns - * @since 3.0.0 + * + * @since 4.0.0 */ -export const cmp: f.comparable = (a: a, b: b): -1 | 0 | 1 => { +export const cmp = (c: Functors.Comparable): Functors.Comparable> => (a: t, b: t) => { if (isNone(a) && isNone(b)) { return 0; - } else if (isNone(a)) { + } + + if (isNone(a)) { return -1; - } else if (isNone(b)) { + } + + if (isNone(b)) { return 1; - } else { - return a as any > b ? 1 : a as any < b ? -1 : 0; } -} -/** - * Returns true if two options are equal. - * - * ```typescript - * import { Option } from 'tiinvo'; - * - * const x = 1 - * const y = null - * const z = undefined - * - * Option.eq(x, y); // false - * Option.eq(x, z); // false - * Option.eq(y, z); // true - * Option.eq(x, x); // true - * ``` - * @param a - * @param b - * @returns - * @since 3.0.0 - */ -export const eq: f.equatable = (a: a, b: a): boolean => isNone(a) && isNone(b) ? true : a === b; + + return c(a, b); +}; + /** - * Returns some if the value is some and the predicate returns true, otherwise returns none. + * Returns true if two results are equal, false otherwise. * - * ```typescript - * import { Option, Number } from 'tiinvo'; + * ```ts + * import { Num, Option } from 'tiinvo'; * - * const p = Number.gt(1); - * const f = Option.filter(p); + * const eq = Option.eq(Num.eq); * - * f(1); // null - * f(2); // 2 - * f(null); // null + * eq(0, 0) // true + * eq(null, undefined) // true + * eq(null, 0) // false + * eq(0, null) // false + * eq(1_000_000, 0) // false * ``` - * @param f - * @returns - * @since 3.0.0 - */ -export const filter = (f: f.predicateE) => (value: a) => (isSome(value) && f(value)) ? value : null; -/** - * Maps an `option` to another `option` if is `some`, otherwise returns none. - * - * ```typescript - * import { Option, Number } from 'tiinvo'; - * - * const m = Option.map(Number.uadd(1)); * - * m(1); // 2 - * m(null); // null - * ``` - * @param map + * @param value * @returns - * @since 3.0.0 + * @since 4.0.0 */ -export const map = (map: f.map) => (value: option) => isSome(value) ? map(value as any) : value; +export const eq = (e: Functors.Equatable): Functors.Equatable> => (a, b) => { + if (isNone(a) && isNone(b)) { + return true; + } + + if (isNone(a) || isNone(b)) { + return false; + } + + return e(a, b); +}; + +//#endregion + +//#region filterables + /** - * Maps an `option` to another `option` if is `some`, otherwise returns `or`. + * Returns `Some` if the value is `Some` and the predicate returns true, otherwise returns `None`. * * ```typescript - * import { Option, Number } from 'tiinvo'; + * import { Option, Num } from 'tiinvo'; * - * const m = Option.mapOr(0, Number.uadd(2)); + * const f = Option.filter(Num.gt(1)); * - * m(1); // 3 - * m(null); // 0 + * f(1) // null + * f(2) // 2 + * f(null) // null * ``` * - * @param or - * @param map - * @returns - * @since 3.0.0 + * @since 4.0.0 */ -export const mapOr = (or: option, map: f.map) => (value: option) => isSome(value) ? map(value as any) : or; +export function filter(f: Functors.Mappable, a: t): t; +export function filter(f: Functors.Mappable): Fn.Unary, t>; +export function filter(f: Functors.Mappable, a?: t): any { + if (arguments.length === 2) { + return isNone(a) ? null : f(a) ? a : null; + } + + return (c: t) => isNone(c) ? null : f(c) ? c : null; +} + +//#endregion + +//#region mappables + /** - * Maps an `option` to another `option` if is `some`, otherwise calls `orElse`. + * Maps an `Option.t` to another `Option.t` if is `Some`, otherwise returns `None`. * * ```typescript - * import { Option, Number } from 'tiinvo'; + * import { Option, Num } from 'tiinvo'; * - * const m = Option.mapOrElse(() => 0, Number.uadd(2)); + * const m = Option.map(Num.add(1)); * - * m(1); // 3 - * m(null); // 0 + * m(1) // 2 + * m(null) // null * ``` * - * @param or - * @param map - * @returns - * @since 3.0.0 + * @since 4.0.0 */ -export const mapOrElse = (or: f.map, map: f.map) => (value: option) => isSome(value) ? map(value as any) : or(); +export const map = (m: Functors.Mappable) => (a: t): t => isNone(a) ? null : m(a) as t; + /** - * Returns the boxed value if the option is `some`, otherwise throws an `Error`. + * Maps an `Option.t` to another `Option.t` if is `Some`, otherwise returns `b`. * * ```typescript - * import { Option } from 'tiinvo'; + * import { Option, Num } from 'tiinvo'; * - * const x = 1 - * const y = null - * const z = undefined + * const m = Option.mapOr(Num.add(2), 0); * - * Option.unwrap(x); // 1 - * Option.unwrap(y); // throws Error - * Option.unwrap(z); // throws Error + * m(1) // 3 + * m(null) // 0 * ``` * - * @param value - * @returns - * @since 3.0.0 + * @since 4.0.0 */ -export const unwrap: f.unwrappable = value => isSome(value) ? value as any : (() => { throw new Error('unwrappable: value is none'); })(); +export const mapOr = (m: Functors.Mappable, b: b) => (a: t): b => isNone(a) ? b : m(a); + +//#endregion + +//#region catchable + /** - * Returns the boxed value if the option is `some`, otherwise returns `or`. + * Calls a function `f` with it's arguments and returns a `Promise>>` * * ```typescript - * import { Option } from 'tiinvo'; + * import { Option, Num } from 'tiinvo'; * - * const x = 1 - * const y = null - * const z = undefined + * const fn = async (arg: number) => { + * if (Num.isEven(arg)) { + * return arg * 2; + * } + * + * throw new Error(`${arg} is not even`); + * } + * + * const safe = Option.tryAsync(fn); * - * const f = Option.unwrapOr(0); + * await safe(2) // 4 + * await safe(3) // null * - * f(x); // 1 - * f(y); // 0 - * f(z); // 0 + * Option.isSome(await safe(2)) // true + * Option.isNone(await safe(3)) // true * ``` * - * @param or + * @param fn * @returns - * @since 3.0.0 + * @since 4.0.0 */ -export const unwrapOr: f.unwrappableOr = or => value => isSome(value) ? value : or; +export const tryAsync = (f: f) => { + return makecatch<(...args: Parameters) => Promise>>>({ + [catchableAsync]() { + return { + catch: async (_err, _args) => null as any, + func: f + }; + } + }); +}; + /** - * Returns the boxed value if the option is `some`, otherwise calls `orElse`. + * Calls a function `f` with it's arguments and returns a `Option.t>` * * ```typescript - * import { Option } from 'tiinvo'; + * import { Option, Num } from 'tiinvo'; * - * const x = 1 - * const y = null - * const z = undefined + * const fn = (arg: number) => { + * if (Num.isEven(arg)) { + * return arg * 2; + * } + * + * throw new Error(`${arg} is not even`); + * } * - * const f = Option.unwrapOrElse(() => 0); + * const safe = Option.trySync(fn); * - * f(x); // 1 - * f(y); // 0 - * f(z); // 0 + * safe(2) // 4 + * safe(3) // null + * + * Option.isSome(safe(2)) // true + * Option.isNone(safe(3)) // true * ``` * - * @param or + * @param fn * @returns - * @since 3.0.0 + * @since 4.0.0 */ -export const unwrapOrElse: f.unwrappableOrElse = (or) => (value) => isSome(value) ? value : or(); +export const trySync = (f: f) => { + return makecatch<(...args: Parameters) => t>>({ + [catchableSync]() { + return { + catch: () => null, + func: f + }; + } + }); +}; + +//#endregion diff --git a/src/pipe.async.ts b/src/pipe.async.ts deleted file mode 100644 index 817d54e..0000000 --- a/src/pipe.async.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { argsOf, returnTypeOf } from './fn'; - -type asChain< - f extends [funcType, ...funcType[]], - g extends funcType[] = tail -> = { - [k in keyof f]: ( - arg: argType - ) => Promise, any>>; -}; - -type argType = F extends (arg: infer A) => any ? A : Else; - -type funcType = (arg: any) => any; - -type lastIndexOf = ((...x: a) => void) extends ( - y: any, - ...z: infer b -) => void - ? b["length"] - : never; - -type lookup = k extends keyof a - ? a[k] - : el; - -type tail = ((...t: a) => void) extends ( - x: any, - ...u: infer b -) => void - ? b - : never; - -type pipeasync = < - f extends [((arg: any) => Promise | (() => Promise)), ...Array<(arg: any) => Promise>] ->( - ...f: f & asChain -) => -f[0] extends () => any ? () => ReturnType]> : f[0] extends (arg: infer U) => any ? (arg: U) => ReturnType]> : never; - -/** - * Creates a pipeline of asynchronous functions. - * - * ```typescript - * import { PipeAsync, Array, Functors } from 'tiinvo/pipe.async' - * - * interface Todo { - * completed: boolean; - * id: number; - * title: string; - * userId: number; - * } - * - * const fetchapi = (url: string) => fetch(url); - * const tojson = (response: Response) => response.json(); - * const filtercompleted: Functors.predicateE = todo => todo.completed; - * const mapid: Functors.map = todo => todo.id; - * - * const mapcompletedids = pipe( - * Array.filter(filtercompleted), - * Array.map(mapid), - * ) - * - * const mapcompleteidsasync = PipeAsync.toasync(mapcompletedids); - * - * const getactivetodoids = PipeAsync.pipeasync( - * fetchapi, - * tojson, - * mapcompleteidsasync, - * ); - * - * await getactivetodoids('https://jsonplaceholder.typicode.com/todos'); - * ``` - * - * @param {Array<(... args: any[]) => any>} args - * @returns - * @since 3.0.0 - */ -export const pipeasync: pipeasync = (...args) => { - const [first, ...othersFns] = args; - return (async (arg: any) => { - const firstvalue = await first(arg); - let latestvalue = firstvalue; - - for (var i = 0; i < othersFns.length; i++) { - latestvalue = await othersFns[i](latestvalue); - } - - return latestvalue; - }) as any; -}; - -/** - * Returns an async version of a function `f` - * - * ```typescript - * import { PipeAsync } from 'tiinvo' - * - * const f = (x: number) => x + 1; - * const g = PipeAsync.toasync(f); - * g(1) // returns a Promise - * ``` - * - * @param f - * @returns - * @since 3.0.0 - */ -export const toasync = any>(f: f): (... args: argsOf) => Promise> => (... args) => Promise.resolve(f(... args)); \ No newline at end of file diff --git a/src/pipe.ts b/src/pipe.ts index f90d5f0..a89e4ac 100644 --- a/src/pipe.ts +++ b/src/pipe.ts @@ -1,8 +1,17 @@ type asChain< f extends [funcType, ...funcType[]], g extends funcType[] = tail + > = { + [k in keyof f]: (arg: argType) => argType, any>; + }; + +type asAsyncChain< + f extends [funcType, ...funcType[]], + g extends funcType[] = tail > = { - [k in keyof f]: (arg: argType) => argType, any>; + [k in keyof f]: ( + arg: argType + ) => Promise, any>>; }; type argType = F extends (arg: infer A) => any ? A : Else; @@ -29,62 +38,68 @@ type tail = ((...t: a) => void) extends ( type pipe = any | (() => any)), ...Array<(arg: any) => any>]>( ...f: f & asChain -) => -f[0] extends () => any ? () => ReturnType]> : f[0] extends (arg: infer U) => any ? (arg: U) => ReturnType]> : never; +) => + f[0] extends () => any ? () => ReturnType]> : f[0] extends (arg: infer U) => any ? (arg: U) => ReturnType]> : never; -// (arg: ArgType) => ReturnType]>; +type pipeasync = < + f extends [((arg: any) => Promise | (() => Promise)), ...Array<(arg: any) => Promise>] + >( + ...f: f & asAsyncChain +) => + f[0] extends () => any ? () => ReturnType]> : f[0] extends (arg: infer U) => any ? (arg: U) => ReturnType]> : never; + +/** + * Same as the sync version, but handles promises. + * + * @since 4.0.0 + */ +export const async: pipeasync = (...args) => { + const [first, ...othersFns] = args; + return (async (arg: any) => { + const firstvalue = await first(arg); + let latestvalue = firstvalue; + + for (var i = 0; i < othersFns.length; i++) { + latestvalue = await othersFns[i](latestvalue); + } + + return latestvalue; + }) as any; +}; /** * Creates a pipeline of synchronous functions - * + * * @example + * * ```ts - * import { Pipe, Number, Maybe } from 'tiinvo/pipe'; + * import { Num, Pipe } from 'tiinvo' * - * const piped = pipe( - * Number.uadd(10), - * Number.umultiply(2), - * Number.iseven, - * Maybe.mapOrElse(() => 'odd', () => 'even'), - * ); + * const vat = Pipe.sync(Num.div(100), Num.mul(22)); * - * piped(2); // `even` - * piped(1); // `even` + * vat(100) // 22 + * vat(150) // 33 + * vat(200) // 44 * ``` * - * @param {Array<(... args: any[]) => any>} args - * @returns + * @since 4.0.0 */ -export const pipe: pipe = (...args) => { +export const sync: pipe = (...args) => { const [first, ...othersFns] = args; type arg0 = argType; switch (args.length) { - case 0: - return (a: arg0) => a; - case 1: - return first; - case 2: - return (arg: arg0) => args[1](first(arg)); - case 3: - return (arg: arg0) => args[2](args[1](first(arg))); - case 4: - return (arg: arg0) => args[3](args[2](args[1](first(arg)))); - case 5: - return (arg: arg0) => args[4](args[3](args[2](args[1](first(arg))))); - case 6: - return (arg: arg0) => args[5](args[4](args[3](args[2](args[1](first(arg)))))); - case 7: - return (arg: arg0) => args[6](args[5](args[4](args[3](args[2](args[1](first(arg))))))); - case 8: - return (arg: arg0) => args[7](args[6](args[5](args[4](args[3](args[2](args[1](first(arg)))))))); - case 9: - return (arg: arg0) => args[8](args[7](args[6](args[5](args[4](args[3](args[2](args[1](first(arg))))))))); - case 10: - return (arg: arg0) => args[9](args[8](args[7](args[6](args[5](args[4](args[3](args[2](args[1](first(arg)))))))))); - default: - return ((arg: arg0) => othersFns.reduce((retval, fn) => fn(retval), first(arg))) as any; + case 0: return (a: arg0) => a; + case 1: return first; + case 2: return (arg: arg0) => args[1](first(arg)); + case 3: return (arg: arg0) => args[2](args[1](first(arg))); + case 4: return (arg: arg0) => args[3](args[2](args[1](first(arg)))); + case 5: return (arg: arg0) => args[4](args[3](args[2](args[1](first(arg))))); + case 6: return (arg: arg0) => args[5](args[4](args[3](args[2](args[1](first(arg)))))); + case 7: return (arg: arg0) => args[6](args[5](args[4](args[3](args[2](args[1](first(arg))))))); + case 8: return (arg: arg0) => args[7](args[6](args[5](args[4](args[3](args[2](args[1](first(arg)))))))); + case 9: return (arg: arg0) => args[8](args[7](args[6](args[5](args[4](args[3](args[2](args[1](first(arg))))))))); + case 10: return (arg: arg0) => args[9](args[8](args[7](args[6](args[5](args[4](args[3](args[2](args[1](first(arg)))))))))); + default: return ((arg: arg0) => othersFns.reduce((retval, fn) => fn(retval), first(arg))) as any; } }; - -export default pipe; diff --git a/src/predicate.ts b/src/predicate.ts index effd505..df60553 100644 --- a/src/predicate.ts +++ b/src/predicate.ts @@ -1,13 +1,16 @@ -import type { predicateA, predicateE } from './functors'; +import type * as Fn from './Fn.js'; +import type * as Functors from './Functors.js'; + +export type t = Functors.Filterable; /** * Combines two or more predicates in one. * If all predicates are satisfied returns `true`, otherwise return `false`. * * ```ts - * import { Predicate, Number } from 'tiinvo'; + * import { Predicate, Num } from 'tiinvo'; * - * const and = Predicate.and(Number.gt(3), Number.lt(10)); + * const and = Predicate.and(Num.gt(3), Num.lt(10)); * * and(5) // true * and(2) // false @@ -15,9 +18,9 @@ import type { predicateA, predicateE } from './functors'; * * @param predicates * @returns - * @since 3.0.0 + * @since 4.0.0 */ -export const and = (...predicates: predicateA[]) => (value: a) => { + export const and = []>(...predicates: pl) => (value: a) => { switch (predicates.length) { case 0: return true; case 1: return predicates[0](value); @@ -33,30 +36,60 @@ export const and = (...predicates: predicateA[]) => (value: a) => { default: return predicates.every(p => p(value)); } }; + /** * Returns true if `b` strictly equals to `a` * * ```ts * import { Predicate } from 'tiinvo'; * - * const eq = Predicate.eq(0) + * const eq0 = Predicate.eq(0) * - * eq(10) // false - * eq(0) // true + * eq0(10) // false + * eq0(0) // true + * Predicate.eq(0, 10) // false + * Predicate.eq(0, 0) // true * ``` * * @param value * @returns - * @since 3.0.0 + * @since 4.0.0 + */ +export function eq(a: a, b: a): boolean; +export function eq(a: a): Fn.Unary; +export function eq(a: a, b?: any): any { + if (arguments.length === 1) { + return (b: a) => a === b; + } + + return a === b; +} + +/** + * Checks if a function could be a predicate + * + * **Important**: the guard will check only if is a unary function (has only one argument), but will not check it's returning type + * + * @example + * + * ```ts + * import { Predicate } from 'tiinvo'; + * + * Predicate.guard((x: number, b: number) => x + b) // false + * Predicate.guard((x: number) => x > 0) // true + * ``` + * + * @since 4.0.0 */ -export const eq = (value: a) => (value2: a) => value === value2; +export const guard: Functors.Guardable> = (x: unknown): x is t => typeof(x) === 'function' && x.length === 1; + /** * Inverts the result of a Predicate * * ```ts - * import { Predicate, Number } from 'tiinvo'; + * import { Predicate, Num } from 'tiinvo'; * - * const i = Predicate.invert(Number.gt(0)) + * const i = Predicate.invert(Num.gt(0)) * * i(10) // false * i(-1) // true @@ -64,9 +97,10 @@ export const eq = (value: a) => (value2: a) => value === value2; * * @param predicate * @returns - * @since 3.0.0 + * @since 4.0.0 */ -export const invert = (predicate: predicateE) => (value: a) => !predicate(value); +export const invert = (p: t): t => (x: a) => !p(x); + /** * Returns true if `b` is not stricly equal to `a` * @@ -75,22 +109,33 @@ export const invert = (predicate: predicateE) => (value: a) => !predicate( * * const neq = Predicate.neq(0) * - * neq(10) // true - * neq(0) // false + * neq(10) // true + * neq(0) // false + * Predicate.neq(0, 10) // true + * Predicate.neq(0, 0) // false * ``` * @param value * @returns - * @since 3.0.0 + * @since 4.0.0 */ -export const neq = (value: a) => (value2: a) => value !== value2; +export function neq(a: a, b: a): boolean; +export function neq(a: a): Fn.Unary; +export function neq(a: a, b?: any): any { + if (arguments.length === 1) { + return (b: a) => a !== b; + } + + return a !== b; +} + /** * Combines two or more predicates in one. * Returns true if none of them is satisfied, otherwise returns false * * ```ts - * import { Predicate, Number } from 'tiinvo'; + * import { Predicate, Num } from 'tiinvo'; * - * const n = Predicate.noneof(Number.gt(0), Number.isEven); + * const n = Predicate.none(Num.gt(0), Num.isEven); * * n(4) // false * n(5) // false @@ -100,9 +145,9 @@ export const neq = (value: a) => (value2: a) => value !== value2; * * @param predicates * @returns - * @since 3.0.0 + * @since 4.0.0 */ -export const noneof = (...predicates: predicateA[]) => (value: a) => { + export const none = []>(...predicates: pl) => (value: a) => { switch (predicates.length) { case 0: return true; case 1: return !predicates[0](value); @@ -118,25 +163,26 @@ export const noneof = (...predicates: predicateA[]) => (value: a) => { default: return predicates.every(p => !p(value)); } } + /** * Combines two or more predicates in one. * Returns true if at least one predicate is satisfied, otherwise returns false * * ```ts - * import { Predicate, Number } from 'tiinvo'; + * import { Predicate, Num } from 'tiinvo'; * - * const or = Predicate.or(Number.gt(0), Number.isEven); + * const or = Predicate.or(Num.gt(0), Num.isEven); * - * or(0) // false - * or(1) // false - * or(2) // true + * or(-1) // false + * or(1) // true + * or(2) // true * ``` * * @param predicates * @returns - * @since 3.0.0 + * @since 4.0.0 */ -export const or = (...predicates: predicateA[]) => (value: a) => { + export const or = []>(...predicates: pl) => (value: a) => { switch (predicates.length) { case 0: return true; case 1: return predicates[0](value); @@ -151,4 +197,4 @@ export const or = (...predicates: predicateA[]) => (value: a) => { case 10: return predicates[0](value) || predicates[1](value) || predicates[2](value) || predicates[3](value) || predicates[4](value) || predicates[5](value) || predicates[6](value) || predicates[7](value) || predicates[8](value) || predicates[9](value); default: return predicates.some(p => p(value)); } -} +} \ No newline at end of file diff --git a/src/promise.ts b/src/promise.ts deleted file mode 100644 index 7a73f0b..0000000 --- a/src/promise.ts +++ /dev/null @@ -1,102 +0,0 @@ -import type * as fn from './fn'; -import type * as r from './result'; - -export type promiseOf = a extends Promise ? b : never; - -export type promisesOf = { - [key in keyof a]: promiseOf -} - -/** - * Creates a Promise that is resolved with an array of results when all of the provided Promises resolve, or rejected when any Promise is rejected. - * - * ```ts - * import { Promise } from 'tiinvo'; - * - * const p1 = Promise.resolve('hello'); - * const p2 = Promise.resolve('world'); - * - * Promise.all([p1, p2]).then(console.log); // => [ 'hello', 'world' ] - * ``` - * @param p - * @returns - * - * @since 3.0.10 - */ -export const all = Promise.all.bind(Promise); -/** - * Creates a new Promise. - * - * ```ts - * import { Promise } from 'tiinvo'; - * - * Promise.make(r => r(1)).then(console.log); // => 1 - * Promise.make((_, r) => r(1)).catch(console.error); // => 1 - * ``` - * - * @param cb - * @returns - * @since 3.4.0 - */ -export const make = (cb: (resolve: fn.unary, reject: fn.unary) => void): Promise => new Promise(cb); -/** - * Maps a promise to a new promise. - * If the promise catches, the new promise returns `err`, otherwise returns `ok`. - * - * ```ts - * import { Promise } from 'tiinvo'; - * - * const p1 = Promise.resolve('hello'); - * const p2 = Promise.reject('whoops'); - * const map = Promise.map((a: string) => a.length); - * - * map(p1).then(console.log); // => 5 - * map(p2).then(console.log); // => Error('whoops') - * ``` - * @param f - * @returns - * - * @since 3.0.10 - */ -export const map = (f: fn.unary) => (p: Promise): Promise> => p.then(f).catch(e => Object.prototype.toString.call(e).includes('Error') ? e : new Error(e)); -/** - * Maps a promise to a new promise if it does not catch, otherwise maps to a promise. - * - * ```ts - * import { Promise } from 'tiinvo'; - * - * const p1 = Promise.resolve('hello'); - * const p2 = Promise.reject('whoops'); - * const fn = (a: string) => a.length; - * const map = Promise.mapOr(0, fn); - * - * map(p1).then(console.log); // => 5 - * map(p2).then(console.log); // => 0 - * - * ``` - * @param or - * @param f - * @returns - * - * @since 3.0.10 - */ -export const mapOr = (or: b, f: fn.unary) => (p: Promise): Promise => p.then(f).catch(() => or); -/** - * Maps a promise to a new promise if it does not catch, otherwise maps to a promise. - * @param or - * @param f - * @returns - */ -export const mapOrElse = (or: fn.nullary, f: fn.unary) => (p: Promise): Promise => p.then(f).catch(or); -/** - * Creates a new resolved promise. - * - * @since 3.0.10 - */ -export const resolve = Promise.resolve.bind(Promise); -/** - * Creates a new rejected promise for the provided reason. - * - * @since 3.0.10 - */ -export const reject = Promise.reject.bind(Promise); diff --git a/src/result.ts b/src/result.ts index acc1a31..96353c3 100644 --- a/src/result.ts +++ b/src/result.ts @@ -1,10 +1,20 @@ -import type * as f from './functors'; +import { make as makecatch } from './Catch.js'; +import type * as Fn from './Fn.js'; +import type * as Functors from './Functors.js'; +import { catchableAsync, catchableSync } from './Functors.js'; -export type ok = a; - -export type err = Error; - -export type result = ok | err; +/** + * Represents an error + */ +export type Err = Error; +/** + * Represents a successful result of an operation + */ +export type Ok = a extends Error ? never : a; +/** + * Could represent both an Error `Err` or a successful result of an operation `Ok` + */ +export type t = Ok | Err; /** * Returns an `err` @@ -23,7 +33,7 @@ export type result = ok | err; * ``` * */ -export const err = (a: unknown): err => { +export const err = (a: unknown): Err => { if (a instanceof Error) { return a; } else if (typeof a === 'object' && a) { @@ -37,6 +47,8 @@ export const err = (a: unknown): err => { return new Error(String(a)); }; +//#region guards + /** * Checks if a value is `err` * @@ -49,12 +61,12 @@ export const err = (a: unknown): err => { * * @param value * @returns - * @since 3.0.0 + * @since 4.0.0 */ -export const isErr = (value: result): value is err => value instanceof Error || (typeof value === 'object' && value !== null && 'message' in value && 'name' in value && 'cause' in value); +export const isErr = (value: unknown): value is Err => value instanceof Error || (typeof value === 'object' && value !== null && 'message' in value && 'name' in value && 'cause' in value); /** - * Checks if a value is `ok` + * Checks if a value is `Ok` * * ```ts * import { Result } from 'tiinvo'; @@ -65,94 +77,146 @@ export const isErr = (value: result): value is err => value instanceof Err * * @param value * @returns - * @since 3.0.0 + * @since 4.0.0 */ -export const isOk = (value: result): value is ok => !isErr(value); +export const isOk = (value: t): value is Ok => !isErr(value); /** - * Checks if a value is `ok` and satisfies the type `guard` + * Checks if a value is `Ok` * * ```ts - * import { Result } from 'tiinvo'; + * import { Num, Result } from 'tiinvo'; * - * const isnumok = Result.isResultOf(Number.guard); + * const guard = Result.isOkOf(Num.guard); * - * isnumok(new Error(10)) // false - * isnumok(1_000_000_000) // true - * isnumok("lorem ipsum") // false + * guard(10) // true + * guard("hello") // false + * guard(new TypeError('aaaa')) // false * ``` * * @param value * @returns - * @since 3.0.0 + * @since 4.0.0 */ -export const isResultOf = (guard: f.guard) => (value: unknown): value is result => isOk(value) ? guard(value) : true; +export const isOkOf = (g: Functors.Guardable) => (x: t): x is a => isErr(x) ? false : g(x); + +//#endregion + +//#region comparables /** - * Compares two `result` + * Compares two results `t` by a given `Comparable`. + * + * Returns -1 if `a` is less than `b`, 0 if `a` is same of `b` and 1 if `a` is greater than `b`. + * + * If `a` is `Err` and `b` is `Ok` returns -1, else if both are `Err` returns 0, else returns 1 + * + * @example * * ```ts - * import { Result } from 'tiinvo'; + * import { Str, Result } from 'tiinvo'; + * + * const cmp = Result.cmp(Str.cmp); * - * Result.cmp(10, 0) // 1 - * Result.cmp(0, 10) // -1 - * Result.cmp(0, 0) // 0 - * Result.cmp(new Error(), 0) // 0 - * Result.cmp(new Error(), new Error()) // 0 - * Result.cmp(0, new Error()) // 0 + * cmp("a", "a") // 0 + * cmp("a", "b") // -1 + * cmp("b", "a") // 1 + * cmp(new Error(), new Error()) // 0 + * cmp(new Error(), "a") // -1 + * cmp("a", new Error()) // 1 * ``` * - * @param value - * @returns - * @since 3.0.0 + * @since 4.0.0 */ -export const cmp: f.comparable = (a: a, b: b): -1 | 0 | 1 => isErr(a) && isErr(b) ? 0 : a as any > b ? 1 : a as any < b ? -1 : 0; +export const cmp = (cmp: Functors.Comparable): Functors.Comparable> => (a, b) => { + if (isErr(a) && isErr(b)) { + return 0; + } + + if (isErr(a)) { + return -1; + } + + if (isErr(b)) { + return 1; + } + + return cmp(a, b); +}; /** - * Filters `ok` with a predicate `p` if `ok` + * Returns true if two results are equal, false otherwise. * * ```ts - * import { Result } from 'tiinvo'; + * import { Num, Result } from 'tiinvo'; * - * const o = Result.filter(Number.isOdd) + * const eq = Result.eq(Num.eq); * - * o(10) // Error("10 is not ok") - * o(11) // 11 - * o(new Error()) // Error() + * eq(0, 0) // true + * eq(new Error(), new TypeError()) // true + * eq(new Error(), 0) // false + * eq(0, new Error()) // false + * eq(1_000_000, 0) // false * ``` * - * If the predicate is satisfied, returns `ok` otherwise `err` * @param value * @returns - * @since 3.0.0 + * @since 4.0.0 */ -export const filter = (predicate: f.predicateE) => (value: result) => isOk(value) && predicate(value) ? value : isErr(value) ? value : new Error(`${value} is not ok`); +export const eq = (eq: Functors.Equatable): Functors.Equatable> => (a, b) => { + if (isErr(a) && isErr(b)) { + return true; + } + + if (isErr(a) || isErr(b)) { + return false; + } + + return eq(a, b); +}; + +//#endregion + +//#region filterables /** - * Returns true if two results are equal, false otherwise. + * Returns `Some` if the value is `Some` and the predicate returns true, otherwise returns `None`. * - * ```ts - * import { Result } from 'tiinvo'; + * ```typescript + * import { Result, Num } from 'tiinvo'; * - * Result.eq(0, 0) // true - * Result.eq(new Error(), new TypeError()) // true - * Result.eq(new Error(), 0) // false - * Result.eq(1_000_000, 0) // false - * ``` + * const f = Result.filter(Num.gt(1)); * - * @param value + * f(1) // Error("Value did not pass filter") + * f(2) // 2 + * f(new TypeError()) // Error("Value did not pass filter") + * ``` + * @param f * @returns - * @since 3.0.0 + * @since 4.0.0 */ -export const eq: f.equatable = (a1: a, a2: a) => isErr(a1) && isErr(a2) ? true : a1 === a2; +export function filter(f: Functors.Filterable, a: t): t; +export function filter(f: Functors.Filterable): Fn.Unary, t>; +export function filter(f: Functors.Filterable, a?: t): any { + const _err = Error("Value did not pass filter"); + if (arguments.length === 2) { + return isErr(a) ? _err : f(a as a) ? a : _err; + } + + return (c: t) => isErr(c) ? _err : f(c) ? c : _err; +} + +//#endregion + +//#region mappables /** - * Maps `result` to `result` if ok, otherwise returns `err` + * Maps `Result.t` to `Result.t` if ok, otherwise returns `err` * * ```ts - * import { Result } from 'tiinvo'; + * import { Num, Result } from 'tiinvo'; * - * const m = Result.map(Number.uadd(10)) + * const m = Result.map(Num.add(10)) * * m(10) // 20 * m(new Error('foobar!')) // Error('foobar!') @@ -160,17 +224,16 @@ export const eq: f.equatable = (a1: a, a2: a) => isErr(a1) && isErr(a2) ? tru * * @param value * @returns - * @since 3.0.0 + * @since 4.0.0 */ -export const map = (map: f.map) => (value: result): result => isErr(value) ? value : map(value); - +export const map = (m: Functors.Mappable) => (a: t): t => isOk(a) ? m(a) : a as any; /** - * Maps `result` to `result` if ok, otherwise returns `or` + * Maps `Result.t` to `Result.t` if ok, otherwise returns `b` * * ```ts - * import { Result } from 'tiinvo'; + * import { Str, Result } from 'tiinvo'; * - * const map = Result.mapOr(0, String.length); + * const map = Result.mapOr(Str.length, 0); * * map('hello') // 5 * map(new Error()) // 0 @@ -178,77 +241,88 @@ export const map = (map: f.map) => (value: result): result => * * @param value * @returns - * @since 3.0.0 + * @since 4.0.0 */ -export const mapOr = (or: b, map: f.map) => (value: result): result => isErr(value) ? or : map(value); +export const mapOr = (m: Functors.Mappable, b: b) => (a: t): b => isOk(a) ? m(a) : b; + +//#endregion + +//#region catchables /** - * Maps `result` to `result` if ok, otherwise calls and returns `or` + * Calls a function `f` with it's arguments and returns a `Promise>>` * - * ```ts - * import { Result } from 'tiinvo'; + * ```typescript + * import { Result, Num } from 'tiinvo'; * - * const map = Result.mapOrElse(() => 0, Boolean.toBit) + * const fn = async (arg: number) => { + * if (Num.isEven(arg)) { + * return arg * 2; + * } + * + * throw new Error(`${arg} is not even`); + * } * - * map(true) // 1 - * map(false) // 0 - * map(new Error()) // 0 - * ``` - * - * @param value - * @returns - * @since 3.0.0 - */ -export const mapOrElse = (or: f.map, map: f.map) => (value: result): result => isErr(value) ? or() : map(value); - -/** - * Unwraps value if ok, otherwise throws + * const safe = Result.tryAsync(fn); * - * ```ts - * import { Result } from 'tiinvo'; + * await safe(2) // 4 + * await safe(3) // Error("3 is not even") * - * Result.unwrap(10) // 10 - * Result.unwrap(new Error()) // throws + * Result.isOk(await safe(2)) // true + * Result.isErr(await safe(3)) // true * ``` * - * @param value + * @param fn * @returns - * @since 3.0.0 + * @since 4.0.0 */ -export const unwrap: f.unwrappable = value => isErr(value) ? (() => { throw value; })() : value; +export const tryAsync = (f: f) => { + return makecatch<(...args: Parameters) => Promise>>>({ + [catchableAsync]() { + return { + catch: async (error) => error, + func: f + }; + } + }); +}; /** - * Unwraps value if ok, otherwise returns or - * - * ```ts - * import { Result } from 'tiinvo'; + * Calls a function `f` with it's arguments and returns a `Promise>>` * - * const u = Result.unwrapOr(0); + * ```typescript + * import { Result, Num } from 'tiinvo'; * - * u(10) // 10 - * u(new Error()) // 0 - * ``` - * - * @param value - * @returns - * @since 3.0.0 - */ -export const unwrapOr: f.unwrappableOr = or => value => isErr(value) ? or : value; - -/** - * Unwraps value if ok, otherwise calls and returns or + * const fn = (arg: number) => { + * if (Num.isEven(arg)) { + * return arg * 2; + * } + * + * throw new Error(`${arg} is not even`); + * } * - * ```ts - * import { Result } from 'tiinvo'; + * const safe = Result.trySync(fn); * - * const u = Result.unwrapOrElse(() => 0); + * safe(2) // 4 + * safe(3) // Error("3 is not even") * - * u(10) // 10 - * u(new Error()) // 0 + * Result.isOk(safe(2)) // true + * Result.isErr(safe(3)) // true * ``` * - * @param value + * @param fn * @returns - * @since 3.0.0 + * @since 4.0.0 */ -export const unwrapOrElse: f.unwrappableOrElse = or => value => isErr(value) ? or() : value; +export const trySync = (f: f) => { + return makecatch<(...args: Parameters) => t>>({ + [catchableSync]() { + return { + catch: (error) => error, + func: f + }; + } + }); +}; + +//#endregion diff --git a/src/set.ts b/src/set.ts deleted file mode 100644 index 01fb2c7..0000000 --- a/src/set.ts +++ /dev/null @@ -1,449 +0,0 @@ -import type * as f from './functors' - -/** - * Set is a collection of unique values. - */ -export type set = Set; - -/** - * Adds a value to the set. - * - * ```typescript - * import { Set } from 'tiinvo'; - * - * const s = Set.make(1, 2, 3); - * const add = Set.add(s); - * const has4 = Set.has(4); - * - * has4(s); // false - * - * add(4); - * - * has4(s); // true - * ``` - * - * @param a - * @returns - * @since 3.5.0 - */ -export const add = (s: set) => (a: a) => s.add(a) - -/** - * Removes all values from the set. - * - * ```typescript - * import { Set } from 'tiinvo'; - * - * const s = Set.make(1, 2, 3); - * - * Set.size(s); // 3 - * Set.clear(s); - * Set.size(s); // 0 - * ``` - * - * @param s - * @returns - * @since 3.5.0 - */ -export const clear = (s: set) => s.clear() - -/** - * Concatenates two sets. - * - * ```typescript - * import { Set } from 'tiinvo'; - * - * const s1 = Set.make(1, 2, 3); - * const s2 = Set.make(4, 5, 6); - * - * Set.concat(s1, s2); // Set(1, 2, 3, 4, 5, 6) - * ``` - * - * @param a - * @returns - * @since 3.5.0 - */ -export const concat = (a: set) => (b: set) => make([...a].concat([...b])); - -/** - * Deletes a value from the set. - * - * ```typescript - * import { Set } from 'tiinvo'; - * - * const s = Set.make(1, 2, 3); - * - * Set.delete(1)(s); // Set(2, 3) - * ``` - * - * @param a - * @returns - */ -export const delete_ = (a: a) => (s: set) => s.delete(a) -export { delete_ as delete }; - -/** - * Returns an iterable of [a,a] pairs for every value a in the set. - * - * ```typescript - * import { Set } from 'tiinvo'; - * - * const s = Set.make(1, 2, 3); - * - * Set.entries(s); // [[1,1], [2,2], [3,3]] - * ``` - * - * @param s - * @returns - */ -export const entries = (s: set) => s.entries() - -/** - * Checks if every value a in a set satisfies the predicate p. - * - * ```typescript - * import { Set, Number } from 'tiinvo'; - * - * const s1 = Set.make(1, 2, 3); - * const s2 = Set.make(1, 2, 0); - * const p = Set.every(Number.gt(0)); - * - * p(s1); // true - * p(s2); // false - * ``` - * - * @param p - * @returns - * @since 3.5.0 - */ -export const every = (p: f.predicateE) => (a: set) => toArray(a).every(p); - -/** - * Filters a set by a predicate. - * - * ```typescript - * import { Set, Number } from 'tiinvo'; - * - * const s1 = Set.make(1, 2, 3); - * - * Set.filter(Number.gt(0))(s1); // Set(1, 2, 3) - * Set.filter(Number.lt(2))(s1); // Set(1) - * ``` - * - * @param f - * @returns - * @since 3.5.0 - */ -export const filter = (f: f.predicateE) => (b: set) => { - const s = new Set(); - b.forEach(a => { - if (f(a)) { - s.add(a); - } - }); - return s; -} - -/** - * Loop over every value a in the set. - * - * ```typescript - * import { Set } from 'tiinvo'; - * - * const s = Set.make(1, 2, 3); - * - * Set.forEach(console.log)(s); - * // 1 - * // 2 - * // 3 - * ``` - * - * @param a - * @returns - */ -export const forEach = (a: ((value: a, value2: a, set: set) => void)) => (s: set) => s.forEach(a) - -/** - * Checks if a is a set - * - * ```typescript - * import { Set } from 'tiinvo'; - * - * Set.guard(Set.make(1, 2, 3)); // true - * Set.guard(1); // false - * ``` - * - * @param a - * @returns - * @since 3.5.0 - */ -export const guard = (a: unknown): a is set => Object.prototype.toString.call(a) === '[object Set]'; - -/** - * Checks if a value is in the set and satisfies the a type guard. - * - * ```typescript - * import { Number, String, Set } from 'tiinvo'; - * - * const s1 = Set.make(1, 2, 3); - * const s2 = Set.make('pineapple', 'on pizza', 'is a huge mistake'); - * const numberSet = Set.guardOf(Number.guard); - * const stringSet = Set.guardOf(String.guard); - * - * console.log(numberSet(s1)); // true - * console.log(numberSet(s2)); // false - * console.log(stringSet(s2)); // true - * console.log(stringSet(s1)); // false - * ``` - * - * @param a - * @returns - * @since 3.5.0 - */ -export const guardOf = (a: f.guard) => (b: unknown): b is set => guard(b) && every(a)(b); - -/** - * Checks if a value is in the set. - * - * ```typescript - * import { Set } from 'tiinvo'; - * - * const s1 = Set.make(1, 2, 3); - * - * Set.has(1)(s1); // true - * Set.has(4)(s1); // false - * ``` - * - * @param a - * @returns - * @since 3.5.0 - */ -export const has = (a: a) => (s: set) => s.has(a) - -/** - * Intersects two sets - * - * ```typescript - * import { Set } from 'tiinvo'; - * - * const s1 = Set.make(1, 2, 3); - * const s2 = Set.make(2, 3, 4); - * - * Set.intersect(s1, s2); // Set(2, 3) - * ``` - * - * @param a - * @param b - * @returns - * @since 3.5.0 - */ -export const intersect = (a: set, b: set): set => { - const s = new Set(); - a.forEach(x => b.has(x as a & b) && s.add(x as a & b)); - return s; -} - -/** - * Makes a new set from an array of values, from a single value, or from a list of arguments. - * - * ```typescript - * import { Set } from 'tiinvo'; - * - * Set.make(1, 2, 3); // Set(1, 2, 3) - * Set.make(1); // Set(1) - * Set.make([1, 2, 3, 4]); // Set(1, 2, 3, 4) - * ``` - * - * @param args - * @param args2 - * @returns - * @since 3.5.0 - */ -export const make = (args: a | readonly a[] | null, ... args2: readonly a[]): set => args ? new Set(Array.isArray(args) ? args.concat(args2) : [args].concat(args2)) : new Set(); - -/** - * Maps a function over a set. - * - * ```typescript - * import { Set, Number } from 'tiinvo'; - * - * const s1 = Set.make(1, 2, 3); - * - * Set.map(Number.uadd(1))(s1); // Set(2, 3, 4) - * ``` - * - * @param map - * @returns - * @since 3.5.0 - */ -export const map = (map: f.map) => (a: set) => { - const s = new Set() - a.forEach(v => s.add(map(v))) - return s -} - -/** - * Checks if no values in the set satisfy the predicate. - * - * ```typescript - * import { Set, Number } from 'tiinvo'; - * - * const s1 = Set.make(1, 2, 3); - * - * Set.none(Number.gt(0))(s1); // false - * Set.none(Number.lt(2))(s1); // false - * Set.none(Number.gt(4))(s1); // true - * ``` - * - * @param p - * @returns - * @since 3.5.0 - */ -export const none = (p: f.predicateE) => (a: set) => !toArray(a).some(p) - -/** - * Reduces a set to a single value. - * - * ```typescript - * import { Set, Number } from 'tiinvo'; - * - * const s1 = Set.make(1, 2, 3); - * - * Set.reduce(Number.badd, 0)(s1); // 6 - * ``` - * - * @param a - * @param b - * @returns - * @since 3.5.0 - */ -export const reduce = (a: ((acc: a, value: a, set: set) => a), b: a) => (s: set) => { - let acc = b; - s.forEach(v => acc = a(acc, v, s)); - return acc; -} - -/** - * Reduces a set to a single value, starting from right. - * - * ```typescript - * import { Set, Number } from 'tiinvo'; - * - * const s1 = Set.make(3, 2, 1); - * - * Set.reduceRight(Number.bsub, 5)(s1); // -1 - * ``` - * - * @param a - * @param b - * @returns - * @since 3.5.0 - */ -export const reduceRight = (a: ((acc: a, value: a, set: set) => a), b: a) => (s: set) => { - let acc = b; - let s1 = toArray(s); - for (let i = s1.length - 1; i >= 0; i--) { - acc = a(acc, s1[i], s); - } - return acc; -} - -/** - * Returns the size of a set - * - * ```typescript - * import { Set } from 'tiinvo'; - * - * const s1 = Set.make(1, 2, 3); - * - * Set.size(s1); // 3 - * ``` - * - * @param s - * @returns - * @since 3.5.0 - */ -export const size = (s: set) => s.size - -/** - * Returns true if some values in the set satisfy the predicate. - * - * ```typescript - * import { Set, Number } from 'tiinvo'; - * - * const s1 = Set.make(1, 2, 3); - * - * Set.some(Number.gt(0))(s1); // true - * Set.some(Number.lt(2))(s1); // true - * Set.some(Number.gt(4))(s1); // false - * ``` - * @param p - * @returns - * @since 3.5.0 - */ -export const some = (p: f.predicateE) => (a: set) => toArray(a).some(p); - -/** - * Converts a set to an array. - * - * ```typescript - * import { Set } from 'tiinvo'; - * - * const s1 = Set.make(1, 2, 3); - * - * Set.toArray(s1); // [1, 2, 3] - * ``` - * - * @param s - * @returns - * @since 3.5.0 - */ -export const toArray = (s: set) => Array.from(s); - -/** - * Returns a new set as the union of a and b sets. - * - * ```ts - * import { Set } from 'tiinvo'; - * - * const a = Set.make(1, 2, 3); - * const b = Set.make(3, 4, 5); - * - * Set.union(a, b); // Set(1, 2, 3, 4, 5) - * ``` - * - * @param a - * @param b - * @returns - * @since 3.5.0 - */ -export const union = (a: set, b: set): set => { - const s = new Set(); - a.forEach(x => s.add(x)); - b.forEach(x => s.add(x)); - return s; -} - -/** - * Returns an iterable of values in the set. - * - * ```ts - * import { Set } from 'tiinvo'; - * - * const s = Set.make(1, 2, 3); - * const v = Set.values(s); - * - * for (const x of v) { - * console.log(x); - * } - * - * // 1 - * // 2 - * // 3 - * ``` - * - * @param s - * @returns - * @since 3.5.0 - */ -export const values = (s: set) => s.values() diff --git a/src/str.ts b/src/str.ts index 88ae63b..1d49883 100644 --- a/src/str.ts +++ b/src/str.ts @@ -1,761 +1,772 @@ -import type * as f from './functors'; -import type * as fn from './fn'; -import type { option } from './option'; +import type * as Fn from './Fn.js'; +import type * as Functors from './Functors.js'; +import type * as Option from './Option.js'; + +export type t = string; + +export type StringReplacer = t | RegExp | Fn.Unary; +export type StringSearcher = t | { + [Symbol.search](string: string): number; +}; +export type StringSplitter = t | { + [Symbol.split](string: string, limit?: number | undefined): string[]; +}; + +//#region guards + +export const guard: Functors.Guardable = (x): x is t => typeof x === 'string'; + +//#endregion + +//#region comparables /** - * Checks if the given value is a string. + * Compares two strings `a` and `b`. * - * ```typescript - * import { String } from 'tiinvo'; + * Returns: * - * String.guard('hello'); // true - * String.guard(10); // false - * ``` + * - 1 if `a` is greater than `b` + * - 0 if `a` is same of `b` + * - -1 if `b` is greater than `a` * - * @since 3.0.0 - */ -export const guard = (value => typeof value === 'string') as f.guard; - -/** - * Compares a to b. + * **Important**: strings are compared as is, no lowercasing is applied * - * ```typescript - * import { String } from 'tiinvo'; + * @example * - * String.cmp('a', 'b'); // -1 - * String.cmp('b', 'a'); // 1 - * String.cmp('a', 'a'); // 0 - * ``` + * ```ts + * import { Str } from 'tiinvo'; * - * @param a - * @param b - * @returns + * Str.cmp('a', 'a') // 0 + * Str.cmp('a', 'b') // -1 + * Str.cmp('b', 'a') // 1 + * ``` + * + * @since 4.0.0 */ -export const cmp: f.comparableE = (a, b) => a > b ? 1 : a < b ? -1 : 0; +export const cmp: Functors.Comparable = (a, b) => a > b ? 1 : a < b ? -1 : 0; /** - * Returns true if a equals to b. + * Returns `true` if two strings are the same * - * ```typescript - * import { String } from 'tiinvo'; + * **Important**: strings are compared as is, no lowercasing is applied * - * String.eq('a', 'a'); // true - * String.eq('a', 'b'); // false + * ```ts + * import { Str } from 'tiinvo'; + * + * Str.eq('a', 'a') // true + * Str.eq('a', 'b') // false + * Str.eq('b', 'a') // false * ``` + * + * @since 4.0.0 */ -export const eq: f.equatableE = (a, b) => a === b; +export const eq: Functors.Equatable = (a, b) => a === b; + +//#endregion + +//#region sortables /** - * Returns true if a string is empty. + * Compares two strings `a` and `b` if `b` is defined, otherwise returns a + * `Unary` function which once called compares `b` and `a` * - * ```typescript - * import { String } from 'tiinvo'; + * Great to sort a string array in ASC direction. * - * String.empty(''); // true - * String.empty('a'); // false - * ``` + * @example * - * @param a - * @returns - */ -export const empty: fn.unary = a => a.length === 0; -/** - * Used to sort strings in ascending order. + * ```ts + * import { Str } from 'tiinvo'; * - * ```typescript - * import { String } from 'tiinvo'; + * const collection = ['a', 'd', 'c', 'e', 'F', 'A']; * - * const array = ['c', 'b', 'a']; - * console.log(array.sort(String.asc)); // ['a', 'b', 'c'] + * collection.sort(Str.asc) // [ "A", "F", "a", "c", "d", "e" ] * ``` * - * @param a - * @param b - * @returns + * @since 4.0.0 */ -export const asc: f.comparableE = (a, b) => cmp(a.toLowerCase(), b.toLowerCase()); +export function asc(a: t, b: t): Functors.ComparableResult; +export function asc(a: t): Fn.Unary; +export function asc(a: t, b?: any): any { + if (guard(b)) { + return cmp(a, b); + } + + return (b: string) => cmp(b, a); +} + /** - * Used to sort strings in descending order. + * Compares two strings `a` and `b` if `b` is defined, otherwise returns a + * `Unary` function which once called compares `b` and `a` * - * ```typescript - * import { String } from 'tiinvo'; + * Great to sort a string array in DESC direction. + * + * @example * - * const array = ['b', 'A', 'c', 'D']; - * console.log(array.sort(String.desc)); // ['D', 'c', 'b', 'A'] + * ```ts + * import { Str } from 'tiinvo'; + * + * const collection = ['a', 'd', 'c', 'e', 'F', 'A']; + * + * collection.sort(Str.desc) // [ "e", "d", "c", "a", "F", "A" ] * ``` * - * @param a - * @param b - * @returns + * @since 4.0.0 */ -export const desc: f.comparableE = (a, b) => cmp(b.toLowerCase(), a.toLowerCase()); +export function desc(a: t, b: t): Functors.ComparableResult; +export function desc(a: t): Fn.Unary; +export function desc(a: t, b?: any): any { + if (guard(b)) { + return cmp(b, a); + } + + return (b: string) => cmp(a, b); +} + +//#endregion //#region native methods /** - * Returns the character at the specified index. - * - * If nothing is found, returns an empty string. + * Formats a string to camel case. * * ```typescript - * import { String } from 'tiinvo'; - * - * String.ucharAt(0)('hello'); // 'h' - * String.ucharAt(1)('hello'); // 'e' - * String.ucharAt(2)('hello'); // 'l' - * String.ucharAt(3)('hello'); // 'l' - * String.ucharAt(4)('hello'); // 'o' - * String.ucharAt(5)('hello'); // '' - * ``` + * import { Str } from 'tiinvo'; * - * @param at + * Str.camel('hello world'); // 'helloWorld' + * ``` + * @param b * @returns + * + * @since 4.0.0 */ -export const ucharAt: fn.unary>> = at => str => str.charAt(at); +export const camel: Fn.Unary = b => b.replace(/\s(.)/g, (_, c) => c.toUpperCase()); /** - * Returns the Unicode value of the character at the specified location. + * Returns the character `Option.t` at the specified index. * - * If there is no character at the specified index, null is returned. + * If `a` and `b` parameters are passed, `b` is the char position for string `a`. * - * ```typescript - * import { String } from 'tiinvo'; - * - * String.ucharCodeAt(0)('hello'); // 72 - * String.ucharCodeAt(1)('hello'); // 101 - * String.ucharCodeAt(2)('hello'); // 108 - * String.ucharCodeAt(3)('hello'); // 108 - * String.ucharCodeAt(4)('hello'); // 111 - * String.ucharCodeAt(5)('hello'); // null - * ``` + * If `b` parameter is not passed, returns a `Unary` function and `a` is the char position for string `b`. * - * @param at - * @returns - */ -export const ucharCodeAt: fn.unary>> = at => str => { - const r = str.charCodeAt(at); - return isNaN(r) ? null : r; -}; - -/** - * Returns a string created from the specified UTF-16 code unit. + * **Important**: if the index is out of range, then `None` is returned. * - * ```typescript - * import { String } from 'tiinvo'; + * @example + * + * ```ts + * import { Str } from 'tiinvo'; * - * String.fromCharCode(72); // 'H' - * String.fromCharCode(0x0041); // 'A' - * String.fromCharCode(0x0061); // 'a' + * Str.charAt("hello", 0) // "h" + * Str.charAt("hello", 10) // null + * Str.charAt(0)("hello") // "h" * ``` * - * @param code - * @returns + * @since 4.0.0 */ -export const fromCharCode: fn.unary = code => String.fromCharCode(code); +export function charAt(a: t, b: number): Option.t; +export function charAt(a: number): Fn.Unary>; +export function charAt(a: any, b?: any): any { + if (guard(a) && typeof b === 'number') { + const c = a.charAt(b); + return c === "" ? null : c; + } else if (typeof a !== 'number') { + return (_: never) => null; + } + + return (b: t) => { + const c = b.charAt(a); + return c === "" ? null : c; + }; +} /** - * Concatenates b and a. + * Returns the char code `Option.t` at the specified index. * - * ```typescript - * import { String } from 'tiinvo'; + * If `a` and `b` parameters are passed, `b` is the char position for string `a`. * - * String.uconcat('a')('b'); // 'ba' - * ``` - * @param a - * @returns - */ -export const uconcat: fn.unary> = a => b => b + a; -/** - * Concatenates a and b. + * If `b` parameter is not passed, returns a `Unary` function and `a` is the char code position for string `b`. * - * ```typescript - * import { String } from 'tiinvo'; + * **Important**: if the index is out of range, then `None` is returned. * - * String.bconcat('a', 'b'); // 'ab' - * ``` - * @param a - * @param b - * @returns - */ -export const bconcat: fn.binary = (a, b) => a + b; -/** - * Returns true if the sequence of elements of searchString converted to a String is the same as the corresponding elements of this object (converted to a String) starting at endPosition – length(this). Otherwise returns false. + * @example * - * ```typescript - * import { String } from 'tiinvo'; + * ```ts + * import { Str } from 'tiinvo'; * - * String.uendsWith('lo')('hello'); // true - * String.uendsWith('ll')('hello'); // false + * Str.charCodeAt("hello", 0) // 104 + * Str.charCodeAt("hello", 10) // null + * Str.charCodeAt(0)("hello") // 104 * ``` * - * @param a - * @returns + * @since 4.0.0 */ -export const uendsWith: fn.unary> = a => b => b.endsWith(a); +export function charCodeAt(a: t, b: number): Option.t; +export function charCodeAt(a: number): Fn.Unary>; +export function charCodeAt(a: any, b?: any): any { + if (guard(a) && typeof b === 'number') { + const c = a.charCodeAt(b); + return c >= 0 ? c : null + } else if (typeof a !== 'number') { + return (_: never) => null; + } + + return (b: t) => { + const c = b.charCodeAt(a); + return c >= 0 ? c : null + }; +} + /** - * Returns true if `a` appears as a substring of `b`, returs false otherwise. - * - * ```typescript - * import { String } from 'tiinvo'; + * Returns a string chars array * - * String.uincludes('lo')('hello'); // true - * String.uincludes('ll')('hello'); // true - * String.uincludes('x')('hello'); // false - * ``` - * @param a - * @returns - */ -export const uincludes: fn.unary> = a => b => b.includes(a); -/** - * Returns true if `a` appears as a substring of `b`, returs false otherwise. + * @example * * ```typescript - * import { String } from 'tiinvo'; + * import { Str } from 'tiinvo'; * - * String.includes('hello', 'lo'); // true - * String.includes('hello', 'll'); // true - * String.includes('hello', 'x'); // false + * Str.chars('hello'); // ['h','e','l','l','o'] * ``` - * @param a - * @returns + * + * @since 4.0.0 */ -export const includes: fn.binary = (a, b) => a.includes(b); +export const chars: Fn.Unary = a => a.split(''); + /** - * Returns the position of the first occurrence of `a` in `b`. + * Returns a concatenated string. * - * ```typescript - * import { String } from 'tiinvo'; + * If `a` and `b` parameters are passed, then the result is `a + b`. * - * String.indexOf('lo')('hello'); // 2 - * String.indexOf('ll')('hello'); // 2 - * String.indexOf('x')('hello'); // -1 - * ``` - * @param a - * @param index The index at which to begin searching the String object. If omitted, search starts at the beginning of the string. - * @returns - */ -export const uindexOf: fn.binary> = (a, index) => b => b.indexOf(a, index ?? 0); -/** - * Returns the position of the first occurrence of `a` in `b`. + * If `b` parameter is not passed, returns a `Unary` function and the result is `b + a`. * - * ```typescript - * import { String } from 'tiinvo'; + * @example + * + * ```ts + * import { Str } from 'tiinvo'; * - * String.indexOf('lo', 'hello'); // 2 - * String.indexOf('ll', 'hello'); // 2 - * String.indexOf('x', 'hello'); // -1 + * Str.concat("hello", "world") // "helloworld" + * Str.concat("world")("hello") // "helloworld" * ``` - * @param a - * @param b - * @param index The index at which to begin searching the String object. If omitted, search starts at the beginning of the string. - * @returns + * + * @since 4.0.0 */ -export const indexOf: fn.ternary = (a, b, index) => a.indexOf(b, index ?? 0); +export function concat(a: t, b: t): t; +export function concat(a: t): Fn.Unary; +export function concat(a: any, b?: any): any { + if (guard(a) && guard(b)) { + return a.concat(b); + } + + return (b: t) => b.concat(a); +} + /** - * Returns the last occurrence of a substring in the string. + * Returns if a string terminates with another one. * - * ```typescript - * import { String } from 'tiinvo'; + * If `a` and `b` parameters are passed, then the result is `a` ends with `b`. * - * String.lastIndexOf('lo')('hello'); // 2 - * String.lastIndexOf('ll')('hello'); // 2 - * String.lastIndexOf('l', 2)('hello'); // 2 - * String.lastIndexOf('l', 3)('hello'); // 3 - * ``` + * If `b` parameter is not passed, returns a `Unary` function and the result is `b` ends with `a`. * - * @param a - * @param index The index at which to begin searching. If omitted, the search begins at the end of the string. - * @returns - */ -export const ulastIndexOf: fn.binary> = (a, index) => b => b.lastIndexOf(a, index ?? b.length); -/** - * Returns the last occurrence of a substring in the string. + * @example * - * ```typescript - * import { String } from 'tiinvo'; + * ```ts + * import { Str } from 'tiinvo'; * - * String.lastIndexOf('lo', 'hello'); // 2 - * String.lastIndexOf('ll', 'hello'); // 2 - * String.lastIndexOf('l', 'hello', 2); // 2 - * String.lastIndexOf('l', 'hello', 3); // 3 + * Str.endsWith("hello", "o") // true + * Str.endsWith("o")("hello") // true * ``` * - * @param a - * @param index The index at which to begin searching. If omitted, the search begins at the end of the string. - * @returns + * @since 4.0.0 */ -export const blastIndexOf: fn.ternary = (a, b, index) => a.lastIndexOf(b, index ?? undefined); +export function endsWith(a: t, b: t): boolean; +export function endsWith(a: t): Fn.Unary; +export function endsWith(a: any, b?: any): any { + if (guard(a) && guard(b)) { + return a.endsWith(b); + } + + return (b: string) => b.endsWith(a); +} + /** - * Matches a string or an object that supports being matched against, and returns an array containing the results of that search, or null if no matches are found. + * Returns if a string includes another one. * - * ```typescript - * import { String } from 'tiinvo'; + * If `a` and `b` parameters are passed, then the result is `a` ends with `b`. * - * String.umatch(/ll/)('hello'); // ['ll'] - * String.umatch(/l/g)('hello'); // ['l', 'l'] - * String.umatch(/x/g)('hello'); // null - * ``` + * If `b` parameter is not passed, returns a `Unary` function and the result is `b` ends with `a`. * - * @param r - * @returns - */ -export const umatch: fn.unary> = r => str => str.match(r); -/** - * Matches a string or an object that supports being matched against, and returns an array containing the results of that search, or null if no matches are found. + * @example * - * ```typescript - * import { String } from 'tiinvo'; + * ```ts + * import { Str } from 'tiinvo'; * - * String.match('hello', /ll/); // ['ll'] - * String.match('hello', /l/g); // ['l', 'l'] - * String.match('hello', /x/g); // null + * Str.includes("hello", "o") // true + * Str.includes("o")("hello") // true * ``` * - * @param r - * @returns + * @since 4.0.0 */ -export const match: fn.binary = (b, r) => b.match(r); +export function includes(a: t, b: t): boolean; +export function includes(a: t): Fn.Unary; +export function includes(a: any, b?: any): any { + if (guard(a) && guard(b)) { + return a.includes(b); + } + + return (b: string) => b.includes(a); +} + /** - * Pads the current string with a given string (possibly repeated) so that the resulting string reaches a given length. The padding is applied from the start (left) of the current string. + * Returns the position of the first occurrence of `a` in `b`. * - * ```typescript - * import { String } from 'tiinvo'; + * If `a` and `b` parameters are passed, then the result is the position of `b` in `a`. * - * String.padStart(5, '0')('1'); // '00001' - * String.padStart(6, '0')('1'); // '000001' - * ``` - * @param ml - * @param fs - * @returns - */ -export const upadstart: fn.binary> = (ml, fs) => b => b.padStart(ml, fs ?? ' '); -/** - * Pads the current string with a given string (possibly repeated) so that the resulting string reaches a given length. The padding is applied from the start (left) of the current string. + * If `b` parameter is not passed, returns a `Unary` function and the result is the position of `a` in `b`. * - * ```typescript - * import { String } from 'tiinvo'; + * @example + * + * ```ts + * import { Str } from 'tiinvo'; * - * String.padStart('1', 5); // ' 1' - * String.padStart('1', 5, '0'); // '00001' - * String.padStart('1', 6, '0'); // '000001' + * Str.indexOf("hello", "l") // 2 + * Str.indexOf("hello", "l", 3) // 3 + * Str.indexOf("l")("hello") // 2 + * Str.indexOf("l")("hello", 3) // 3 * ``` - * @param ml - * @param fs - * @returns + * + * @since 4.0.0 */ -export const padstart: fn.ternary = (b, ml, fs) => b.padStart(ml, fs ?? ' '); +export function indexOf(a: t, b: t, i?: number): Option.t; +export function indexOf(a: t): (b: t, i?: number) => Option.t; +export function indexOf(a: any, b?: any, i?: number): any { + if (guard(a) && guard(b)) { + return a.indexOf(b, i); + } + + return (b: t, i: number) => b.indexOf(a, i); +} /** - * Pads the current string with a given string (possibly repeated) so that the resulting string reaches a given length. The padding is applied from the end (right) of the current string. + * Returns last string position of 'b' in 'a'. * - * ```typescript - * import { String } from 'tiinvo'; + * If `a` and `b` parameters are passed, then the result is `b` last position `a`. * - * String.upadEnd(5, '0')('1'); // '10000' - * String.upadEnd(6, '0')('1'); // '100000' - * ``` - * @param ml - * @param fs - * @returns - */ -export const upadEnd: fn.binary> = (ml, fs) => b => b.padEnd(ml, fs ?? ' '); -/** - * Pads the current string with a given string (possibly repeated) so that the resulting string reaches a given length. The padding is applied from the end (right) of the current string. + * If `b` parameter is not passed, returns a `Unary` function and the result is `a` last position `b`. * - * ```typescript - * import { String } from 'tiinvo'; + * @example * - * String.padEnd('1', 5); // '1 ' - * String.padEnd('1', 5, '0'); // '10000' - * String.padEnd('1', 6, '0'); // '100000' + * ```ts + * import { Str } from 'tiinvo'; + * + * Str.lastIndexOf("hello", "l") // 3 + * Str.lastIndexOf("l")("hello") // 3 * ``` - * @param b - * @param ml - * @param fs - * @returns + * + * @since 4.0.0 */ -export const padEnd: fn.ternary = (b, ml, fs) => b.padEnd(ml, fs ?? ` `); +export function lastIndexOf(a: t, b: t, p?: number): Option.t; +export function lastIndexOf(a: t): (b: t, p?: number) => Option.t; +export function lastIndexOf(a: any, b?: any, p?: any): any { + if (guard(a) && guard(b)) { + return a.lastIndexOf(b, p); + } + + return (b: t, p?: number) => b.lastIndexOf(a, p); +} + /** - * Returns a String value that is made from count copies appended together. If count is 0, the empty string is returned. + * Returns a string length + * + * @example * * ```typescript - * import { String } from 'tiinvo'; + * import { Str } from 'tiinvo'; * - * String.urepeat(3)('x'); // 'xxx' - * String.urepeat(0)('x'); // '' + * Str.length('hello'); // 5 * ``` * - * @param n - * @returns + * @since 4.0.0 */ -export const urepeat: fn.unary> = n => b => b.repeat(n); +export const length: Fn.Unary = a => a.length; + /** - * Returns a String value that is made from count copies appended together. If count is 0, the empty string is returned. + * Returns an array of lines in a string. * * ```typescript - * import { String } from 'tiinvo'; + * import { Str } from 'tiinvo'; * - * String.repeat('x', 3); // 'xxx' - * String.repeat('x', 0); // '' + * Str.lines('hello\nworld'); // ['hello', 'world'] * ``` * - * @param b - * @param n - * @returns - */ -export const brepeat: fn.binary = (b, n) => b.repeat(n); -/** - * Replaces text in a string, using a regular expression or search string. + * @param b + * @returns * - * ```typescript - * import { String } from 'tiinvo'; + * @since 4.0.0 * - * String.ureplace('ll', 'hh')('hello'); // 'hehho' - * String.ureplace('ll', arg => arg.length)('hello'); // 'he2o' - * ``` - * @param rx - * @param r - * @returns */ -export const ureplace: fn.binary string), fn.unary> = (rx, r) => b => b.replace(rx, r as string); +export const lines: Fn.Unary = x => x.split(/\r?\n/); + /** - * Replaces text in a string, using a regular expression or search string. + * Returns a lowercased string + * + * @example * * ```typescript - * import { String } from 'tiinvo'; + * import { Str } from 'tiinvo'; * - * String.replace('hello', 'll', 'hh'); // 'hehho' - * String.replace('hello', 'll', arg => arg.length); // 'he2o' + * Str.lower('HELLO'); // "hello" * ``` - * @param b - * @param rx - * @param r - * @returns + * + * @since 4.0.0 */ -export const replace: fn.ternary string), string> = (b, rx, r) => b.replace(rx, r as string); +export const lower: Fn.Unary = a => a.toLowerCase(); + /** - * Reverses a string. + * Returns if a string includes another one. * - * ```typescript - * import { String } from 'tiinvo'; + * If `a` and `b` parameters are passed, then the result is `a` ends with `b`. * - * String.reverse('hello'); // 'olleh' - * ``` + * If `b` parameter is not passed, returns a `Unary` function and the result is `b` ends with `a`. * - * @param a - * @returns - * @since 3.7.0 - */ -export const reverse: fn.unary = a => a.split('').reverse().join(''); -/** - * Finds the first substring match in a regular expression search. + * @example * - * ```typescript - * import { String } from 'tiinvo'; + * ```ts + * import { Str } from 'tiinvo'; * - * String.usearch('ll')('hello'); // 1 - * String.usearch(/l/g)('hello'); // 1 + * Str.match("hello", "o") // ['o'] + * Str.match("o")("hello") // ['o'] * ``` - * @param rx - * @returns + * + * @since 4.0.0 */ -export const usearch: fn.unary> = rx => b => b.search(rx); +export function match(a: t, b: t | RegExp): Option.t; +export function match(a: t | RegExp): Fn.Unary>; +export function match(a: any, b?: any): any { + if (guard(a) && (guard(b) || b instanceof RegExp)) { + return a.match(b); + } + + return (b: string) => b.match(a); +} + /** - * Finds the first substring match in a regular expression search. + * Pads the current string with a given string (possibly repeated) so that the resulting string reaches a given length. The padding is applied from the end (right) of the current string. * - * ```typescript - * import { String } from 'tiinvo'; + * If `a` and `b` parameters are passed, then the result is `a` padded by `b`. * - * String.search('hello', 'll'); // 1 - * String.search('hello', /l/g); // 1 - * ``` - * @param b - * @param rx - * @returns - */ -export const search: fn.binary = (b, rx) => b.search(rx); -/** - * Returns a section of a string. + * If `b` parameter is not passed, returns a `Unary` function and the result is `b` padded by `a`. * - * ```typescript - * import { String } from 'tiinvo'; + * @example * - * String.uslice(1, 3)('hello'); // 'el' - * String.uslice(1)('hello'); // 'ello' + * ```ts + * import { Str } from 'tiinvo'; + * + * Str.padEnd("a", 5) // "a " + * Str.padEnd("a", 5, "b") // "abbbb" + * Str.padEnd(5)("a") // "a " + * Str.padEnd(5, "b")("a") // "abbbb" + * Str.padEnd(5)("a", "b") // "abbbb" * ``` - * @param start - * @param end - * @returns + * + * @since 4.0.0 */ -export const uslice: fn.binary> = (start, end) => b => b.slice(start ?? undefined, end ?? undefined); +export function padEnd(a: t, b: number, c?: t): t; +export function padEnd(a: number): (b: t, d?: t) => t; +export function padEnd(a: number, b?: t): (b: t) => t; +export function padEnd(a: any, b?: any, c?: any): any { + if (guard(a) && typeof b === 'number') { + return a.padEnd(b, c); + } + + return (x: string, y?: string) => x.padEnd(a, b ?? y); +} + /** - * Returns a section of a string. + * Formats a string to pascal case. * * ```typescript - * import { String } from 'tiinvo'; + * import { Str } from 'tiinvo'; * - * String.slice('hello', 1, 3); // 'el' - * String.slice('hello', 1); // 'ello' + * Str.pascal('hello world'); // 'HelloWorld' * ``` * @param b - * @param start - * @param end * @returns */ -export const slice: fn.ternary = (b, start, end) => b.slice(start ?? undefined, end ?? undefined); +export const pascal: Fn.Unary = b => b.replace(/\s(.)/g, (_, c) => c.toUpperCase()).replace(/^(.)/, (_, c) => c.toUpperCase()); + /** - * Split `b` into substrings using the specified separator `rx` and return them as an array. + * Pads the current string with a given string (possibly repeated) so that the resulting string reaches a given length. The padding is applied from the start (left) of the current string. * - * ```typescript - * import { String } from 'tiinvo'; + * If `a` and `b` parameters are passed, then the result is `a` padded by `b`. * - * String.usplit(' ')('hello world'); // ['hello', 'world'] - * String.usplit(/\s+/)('hello world'); // ['hello', 'world'] - * ``` + * If `b` parameter is not passed, returns a `Unary` function and the result is `b` padded by `a`. * - * @param rx - * @param n - * @returns - */ -export const usplit: fn.binary> = (rx, n) => b => b.split(rx as string, n ?? undefined); -/** - * Split `b` into substrings using the specified separator `rx` and return them as an array. + * @example * - * ```typescript - * import { String } from 'tiinvo'; + * ```ts + * import { Str } from 'tiinvo'; * - * String.split('hello', ' '); // ['hello', 'world'] - * String.split('hello', /\s+/); // ['hello', 'world'] + * Str.padStart("a", 5) // " a" + * Str.padStart("a", 5, "b") // "bbbba" + * Str.padStart(5)("a") // " a" + * Str.padStart(5, "b")("a") // "bbbba" + * Str.padStart(5)("a", "b") // "bbbba" * ``` - * @param b - * @param rx - * @param n - * @returns + * + * @since 4.0.0 */ -export const split: fn.ternary = (b, rx, n) => b.split(rx as string, n ?? undefined); +export function padStart(a: t, b: number, c?: t): t; +export function padStart(a: number): (b: t, d?: t) => t; +export function padStart(a: number, b?: t): (b: t) => t; +export function padStart(a: any, b?: any, c?: any): any { + if (guard(a) && typeof b === 'number') { + return a.padStart(b, c); + } + + return (x: string, y?: string) => x.padStart(a, b ?? y); +} + /** - * Removes the leading and trailing white space and line terminator characters from a string. + * Returns a String value that is made from count copies appended together. If count is 0, the empty string is returned * - * ```typescript - * import { String } from 'tiinvo'; + * If `a` and `b` parameters are passed, then the result is `a` repeated by `b` times. * - * String.trim(' hello world '); // 'hello world' - * ``` + * If `b` parameter is not passed, returns a `Unary` function and the result is `b` repeated by `a` times. * - * @param b - * @returns - */ -export const trim: fn.unary = b => b.trim(); -/** - * Removes the trailing white space and line terminator characters from a string. + * @example * - * ```typescript - * import { String } from 'tiinvo'; + * ```ts + * import { Str } from 'tiinvo'; * - * String.trimEnd(' hello world '); // ' hello world' + * Str.repeat("a", 5) // "aaaaa" + * Str.repeat(5)("a") // "aaaaa" * ``` - * @param b - * @returns + * + * @since 4.0.0 */ -export const trimEnd: fn.unary = b => b.trimEnd(); +export function repeat(a: t, b: number): t; +export function repeat(a: number): (b: t) => t; +export function repeat(a: any, b?: any): any { + if (guard(a) && typeof b === 'number') { + return a.repeat(b); + } + + return (b: string) => b.repeat(a); +} + /** - * Removes the leading white space and line terminator characters from a string. + * Returns a String value that is made from count copies appended together. If count is 0, the empty string is returned * - * ```typescript - * import { String } from 'tiinvo'; + * If `a` and `b` parameters are passed, then the result is `a` repeated by `b` times. * - * String.trimStart(' hello world '); // 'hello world ' - * ``` + * If `b` parameter is not passed, returns a `Unary` function and the result is `b` repeated by `a` times. * - * @param b - * @returns - */ -export const trimStart: fn.unary = b => b.trimStart(); -/** - * Returns a string length + * @example * - * ```typescript - * import { String } from 'tiinvo'; + * ```ts + * import { Str } from 'tiinvo'; * - * String.length('hello'); // 5 + * Str.replace("hello", "l", "e") // "heelo" + * Str.replace("l", "e")("hello") // "heelo" * ``` * - * @param b - * @returns - * @since 3.0.0 + * @since 4.0.0 */ -export const length: fn.unary = b => b.length; +export function replace(a: t, b: t, c: StringReplacer): t; +export function replace(a: t, b: StringReplacer): (b: t) => t; +export function replace(a: any, b?: any, c?: any): any { + if (guard(a) && guard(b) && typeof c !== 'undefined') { + return a.replace(b, c); + } + + return (x: t) => x.replace(a, b); +} + /** - * Converts all the alphabetic characters in a string to lowercase. + * Reverses a string. + * + * @example * * ```typescript - * import { String } from 'tiinvo'; + * import { Str } from 'tiinvo'; * - * String.lower('HELLO WORLD'); // 'hello world' + * Str.reverse('hello'); // 'olleh' * ``` - * @param b - * @returns + * + * @since 4.0.0 */ -export const lower: fn.unary = b => b.toLowerCase(); +export const reverse: Fn.Unary = a => a.split('').reverse().join(''); + /** - * Converts all the alphabetic characters in a string to uppercase. + * Finds the first substring match in a regular expression search. * - * ```typescript - * import { String } from 'tiinvo'; + * If `a` and `b` parameters are passed, then the result is `a` is searched by `b`. + * + * If `b` parameter is not passed, returns a `Unary` function and the result is `b` is searched by `a`. + * + * **Important**: it differs from plain js String.prototype.search, since it will return `None` if the index is below `0` * - * String.upper('hello world'); // 'HELLO WORLD' + * @example + * + * ```ts + * import { Str } from 'tiinvo'; + * + * Str.search("hello", "l") // 2 + * Str.search("l")("hello") // 2 * ``` * - * @param b - * @returns + * @since 4.0.0 */ -export const upper: fn.unary = b => b.toUpperCase(); +export function search(a: t, b: StringSearcher): Option.t; +export function search(a: StringSearcher): (b: t) => Option.t; +export function search(a: any, b?: any): any { + const m: Functors.Mappable> = x => x >= 0 ? x : null; -//#endregion + if (guard(a) && typeof b !== 'undefined') { + return m(a.search(b)); + } -//#region case formatting + return (b: t) => m(b.search(a)); +} /** - * Formats a string to camel case. + * Returns a section of a string. * - * ```typescript - * import { String } from 'tiinvo'; + * If `a` and `b` parameters are passed, then the result is `a` a slice to `b`. * - * String.camelCase('hello world'); // 'helloWorld' - * ``` - * @param b - * @returns - */ -export const camel: fn.unary = b => b.replace(/\s(.)/g, (_, c) => c.toUpperCase()); -/** - * Formats a string to pascal case. + * If `a` and `b` and `c` parameters are passed, then the result is `a` a slice from `b` ending to `c` position. * - * ```typescript - * import { String } from 'tiinvo'; + * If `b` parameter is not passed, returns a `Unary` function and the result is `b` is searched by `a`. * - * String.pascal('hello world'); // 'HelloWorld' - * ``` - * @param b - * @returns - */ -export const pascal: fn.unary = b => b.replace(/\s(.)/g, (_, c) => c.toUpperCase()).replace(/^(.)/, (_, c) => c.toUpperCase()); -/** - * Formats a string to kebab case. + * **Important**: it differs from plain js String.prototype.search, since it will return `None` if the index is below `0` * - * ```typescript - * import { String } from 'tiinvo'; + * @example + * + * ```ts + * import { Str } from 'tiinvo'; * - * String.kebab('hello world'); // 'hello-world' + * Str.slice("hello", 1) // "ello" + * Str.slice("hello", 1, 3) // "el" + * Str.slice(1)("hello") // "ello" + * Str.slice(1, 3)("hello") // "el" * ``` * - * @param b - * @returns + * @since 4.0.0 */ -export const kebab: fn.unary = b => b.replace(/[A-Z]/g, word => ' ' + word.replace(/[A-Z]/, b => b.toLowerCase())).replace(/\s/g, '-'); +export function slice(a: t, b: number, c?: number): string; +export function slice(a: number, b?: number): (b: t) => string; +export function slice(a: any, b?: any, c?: any): any { + if (guard(a) && typeof b === 'number') { + return a.slice(b, c); + } + + return (x: t, y?: number) => x.slice(a, b ?? y); +} + /** - * Formats a string to snake case. + * Returns a section of a string. * - * ```typescript - * import { String } from 'tiinvo'; + * If `a` and `b` parameters are passed, then the result is `a` a slice to `b`. + * + * If `a` and `b` and `c` parameters are passed, then the result is `a` a slice from `b` ending to `c` position. + * + * If `b` parameter is not passed, returns a `Unary` function and the result is `b` is searched by `a`. + * + * **Important**: it differs from plain js String.prototype.search, since it will return `None` if the index is below `0` + * + * @example * - * String.snake('hello world'); // 'hello_world' + * ```ts + * import { Str } from 'tiinvo'; + * + * Str.split("hello world", " ") // ["hello", "world"] + * Str.split(" ")("hello world") // ["hello", "world"] + * Str.split(" ", 1)("hello world") // ["hello"] * ``` * - * @param b - * @returns + * @since 4.0.0 */ -export const snake: fn.unary = b => b.replace(/[A-Z]/g, word => ' ' + word.replace(/[A-Z]/, b => b.toLowerCase())).replace(/\s/g, '_'); +export function split(a: t, b: StringSplitter, c?: t): t[]; +export function split(a: StringSplitter, b?: number): Fn.Unary; +export function split(a: any, b?: any, c?: any): any { + if (guard(a) && (guard(b) || b instanceof RegExp || (!!b && typeof b === 'object' && Symbol.split in b))) { + return a.split(b, c); + } -//#endregion + return (x: t, y?: number) => x.split(a, b ?? y); +} /** - * Returns an array of chars. + * Trims a string. + * + * @example * * ```typescript - * import { String } from 'tiinvo'; + * import { Str } from 'tiinvo'; * - * String.chars('hello'); // ['h', 'e', 'l', 'l', 'o'] + * Str.trim(' hello world '); // 'hello world' * ``` * - * @param b - * @returns - * - * @since 3.1.0 + * @since 4.0.0 */ -export const chars = usplit(''); +export const trim: Fn.Unary = a => a.trim(); + /** - * Returns an array of lines in a string. + * Trims a string at it's end. + * + * @example * * ```typescript - * import { String } from 'tiinvo'; + * import { Str } from 'tiinvo'; * - * String.split('hello\nworld'); // ['hello', 'world'] + * Str.trimEnd(' hello world '); // ' hello world' * ``` * - * @param b - * @returns - * - * @since 3.1.0 - * + * @since 4.0.0 */ -export const lines = usplit(/\r?\n/); +export const trimEnd: Fn.Unary = a => a.trimEnd(); /** - * Returns an array of words in a string. + * Trims a string at it's start. + * + * @example * * ```typescript - * import { String } from 'tiinvo'; + * import { Str } from 'tiinvo'; * - * String.words('hello world'); // ['hello', 'world'] + * Str.trimStart(' hello world '); // 'hello world ' * ``` * - * @param b - * @returns - * - * @since 3.1.0 + * @since 4.0.0 */ -export const words = usplit(/\s+/m); +export const trimStart: Fn.Unary = a => a.trimStart(); /** - * Adds a `prefix` to a `sub` - * @param sub - * @param prefix - * @returns - * @since 3.9.0 + * Returns a uppercased string * * @example * - * ```ts + * ```typescript * import { Str } from 'tiinvo'; * - * Str.prefix('hello', 'world') // 'worldhello'; + * Str.upper('hello'); // "HELLO" * ``` + * + * @since 4.0.0 */ -export const prefix = (sub: string, prefix: string) => prefix + sub; +export const upper: Fn.Unary = a => a.toUpperCase(); + /** - * Adds a `suffix` to a `sub` - * @param sub - * @param suffix - * @returns - * @since 3.9.0 - * - * @example + * Returns an array of words in a string. * - * ```ts + * ```typescript * import { Str } from 'tiinvo'; * - * Str.suffix('hello', 'world') // 'helloworld'; + * Str.words('hello world'); // ['hello', 'world'] * ``` + * + * @param b + * @returns + * + * @since 4.0.0 */ -export const suffix = (sub: string, suffix: string) => sub + suffix; -/** - * Curried version of prefix - * @param prefix the prefix - * @returns - */ -export const uprefix = (prefix: string) => (sub: string) => prefix + sub; -/** - * Curried version of suffix - * @param suffix the suffix - * @returns - */ -export const usuffix = (suffix: string) => (sub: string) => sub + suffix; \ No newline at end of file +export const words: Fn.Unary = x => x.split(/\s+/m); + +//#endregion diff --git a/src/try.ts b/src/try.ts deleted file mode 100644 index a0b212d..0000000 --- a/src/try.ts +++ /dev/null @@ -1,167 +0,0 @@ -import type * as fn from './fn'; -import type * as m from './maybe'; -import type * as o from './option'; -import { type result as _result, type ok, type err } from './result'; - -const _err = (a: unknown): err => { - if (a instanceof Error) { - return a; - } else if (typeof a === 'object' && a) { - const err = new Error(); - for (const [key, value] of Object.entries(a)) { - (err as any)[key] = value; - } - return err; - } - - return new Error(String(a)); -}; - -/** - * Calls a function `fn` with it's arguments and returns a `maybe>` - * - * ```typescript - * import { Try, Maybe, Number } from 'tiinvo'; - * - * const fn = (arg: number) => { - * if (Number.isEven(arg)) { - * return arg * 2; - * } - * - * throw new Error(`${arg} is not even`); - * } - * - * const safe = Try.maybe(fn); - * - * console.log(safe(2)); // 4 - * console.log(safe(3)); // null - * - * console.log(Maybe.isJust(safe(2))); // true - * console.log(Maybe.isNothing(safe(3))); // true - * ``` - * - * @param fn - * @returns - * @since 3.0.0 - */ -export const maybe = any | never>(fn: fn) => (... args: fn.argsOf): m.maybe> => { - try { - return fn(... args) as m.just>; - } catch (e) { - return null as m.nothing; - } -}; - -/** - * The async version of `trymaybe` - * @param fn - * @returns - * @since 3.0.0 - */ -export const maybeAsync = any | Promise | never>(fn: fn) => async (... args: fn.argsOf): Promise>> => { - try { - return await fn(... args) as m.just>; - } catch (e) { - return null as m.nothing; - } -}; - -/** - * Calls a function `fn` with it's arguments and returns a `option>` - * - * ```typescript - * import { Try, Option, Number } from 'tiinvo'; - * - * const fn = (arg: number) => { - * if (Number.isEven(arg)) { - * return arg * 2; - * } - * - * throw new Error(`${arg} is not even`); - * } - * - * const safe = Try.option(fn); - * - * console.log(safe(2)); // 4 - * console.log(safe(3)); // null - * - * console.log(Option.isSome(safe(2))); // true - * console.log(Option.isNone(safe(3))); // true - * ``` - * - * @param fn - * @returns - * @since 3.0.0 - */ -export const option = any | never>(fn: fn) => (... args: fn.argsOf): o.option> => { - try { - return fn(... args) as o.some>; - } catch (e) { - return null as o.none; - } -}; - -/** - * The async version of `tryoption` - * - * @param fn - * @returns - * @since 3.0.0 - */ -export const optionAsync = any | Promise | never>(fn: fn) => async (... args: fn.argsOf): Promise>> => { - try { - return await fn(... args) as o.some>; - } catch (e) { - return null as o.none; - } -}; - -/** - * Calls a function `fn` with it's arguments and returns a `result>` - * - * ```typescript - * import { Try, Option, Number } from 'tiinvo'; - * - * const fn = (arg: number) => { - * if (Number.isEven(arg)) { - * return arg * 2; - * } - * - * throw new Error(`${arg} is not even`); - * } - * - * const safe = Try.result(fn); - * - * console.log(safe(2)); // 4 - * console.log(safe(3)); // Error: 3 is not even - * - * console.log(r.isOk(safe(2))); // true - * console.log(r.isErr(safe(3))); // true - * ``` - * - * @param fn - * @returns - * @since 3.0.0 - */ -export const result = any | never>(fn: fn) => (... args: fn.argsOf): _result> => { - try { - return fn(... args) as ok>; - } catch (error) { - return _err(error) as err; - } -} - -/** - * The async version of `tryresult` - * - * @param fn - * @returns - * @since 3.0.0 - */ -export const resultAsync = any | Promise | never>(fn: fn) => async (... args: fn.argsOf): Promise<_result>> => { - try { - return await fn(... args) as ok>; - } catch (error) { - return _err(error) as err; - } -} diff --git a/src/tuple.ts b/src/tuple.ts deleted file mode 100644 index 771726f..0000000 --- a/src/tuple.ts +++ /dev/null @@ -1,210 +0,0 @@ -import type * as f from './functors'; -import type * as fn from './fn'; -import type * as o from './option'; - -export type tuple = { - [K in keyof T]: T[K] -} - -/** - * Compares two tuples. - * - * ```typescript - * import { Tuple } from 'tiinvo'; - * - * const a: Tuple.tuple<[number, number, number]> = [1, 2, 3] - * const b: Tuple.tuple<[number, number, number]> = [1, 2, 3] - * const c: Tuple.tuple<[number, number, number]> = [1, 2, 4] - * const d: Tuple.tuple<[number, number]> = [1, 2]; - * - * console.log(Tuple.cmp(a, b)) // 0 - * console.log(Tuple.cmp(a, c)) // -1 - * console.log(Tuple.cmp(c, a)) // 1 - * console.log(Tuple.cmp(d, a)) // -1 - * console.log(Tuple.cmp(a, d)) // 1 - * console.log(Tuple.cmp(null, a)) // -1 - * console.log(Tuple.cmp(null, undefined)) // 0 - * ``` - * - * @param a - * @param b - * @returns - */ -export const cmp: f.comparable = (a, b) => { - const isa = Array.isArray(a); - const isb = Array.isArray(b); - if (isa && isb) { - const l1 = a.length; - const l2 = b.length; - - if (l1 !== l2) { - return l1 > l2 ? 1 : -1; - } else { - let r1 = 0; - let r2 = 0; - - for (let i = 0; i < l1; i++) { - r1 += a[i] > b[i] ? 1 : a[i] < b[i] ? -1 : 0; - r2 += a[i] < b[i] ? 1 : a[i] > b[i] ? -1 : 0; - } - - return r1 - r2 === 0 ? 0 : r1 > r2 ? 1 : -1; - } - } else if (!isa && !isb) { - return 0; - } else { - return -1; - } -} - -/** - * Returns `true` if the tuple are equal, otherwise `false`. - * - * ```typescript - * import { Tuple } from 'tiinvo'; - * - * const a: Tuple.tuple<[number, number, number]> = [1, 2, 3] - * const b: Tuple.tuple<[number, number, number]> = [1, 2, 3] - * const c: Tuple.tuple<[number, number, number]> = [1, 2, 4] - * const d: Tuple.tuple<[number, number]> = [1, 2]; - * - * console.log(Tuple.eq(a, b)) // true - * console.log(Tuple.eq(a, c)) // false - * console.log(Tuple.eq(c, a)) // false - * console.log(Tuple.eq(d, a)) // false - * console.log(Tuple.eq(a, d)) // false - * console.log(Tuple.eq(null, a)) // false - * console.log(Tuple.eq(null, undefined)) // true - * ``` - * - * @param a - * @param b - * @returns - * @since 3.0.0 - */ -export const eq: f.equatable = (a, b) => cmp(a, b) === 0; - -/** - * Makes a guard function from a tuple of guards. - * - * ```typescript - * import { Number, String, Tuple } from 'tiinvo'; - * - * const guard = Tuple.guardOf(Number.guard, String.guard, Number.guard); - * - * guard([1, 'a', 2]); // true - * guard([1, 'a', 'b']); // false - * ``` - * - * @param guards - * @returns - * @since 3.0.0 - */ -export const guardOf = []>(... guards: g) => (value: unknown): value is tuple> => - Array.isArray(value) && guards.every((guard, i) => guard(value[i])); - -/** - * Gets the first element `a` of a tuple as an option. - * - * ```typescript - * import { Tuple } from 'tiinvo'; - * - * Tuple.get(2)([1, 2, 3]); // 3 - * Tuple.get(2)([1, 2]); // null - * ``` - * - * @param i - * @returns - * @since 3.0.0 - */ -export const get = (i: i) => >(a: a): o.option => a[i] ?? null; - -/** - * Intersects two tuples `a` and `b` returning a new `tuple` - * - * ```typescript - * import { Tuple } from 'tiinvo'; - * - * Tuple.intersect([1, 2, 3])([1, 2, 3]); // [1, 2, 3] - * Tuple.intersect([1, 2, 3])([1, 2, 4]); // [1, 2] - * Tuple.intersect([1, 2, 3])([1, 2, 4, 5]); // [1, 2] - * ``` - * - * @param a - * @returns - * @since 3.0.0 - */ -export const intersect = >(a: a) => >(b: b): a & b => { - const c = [] as unknown as a & b; - const l = Math.min(a.length, b.length); - - for (let i = 0; i < l; i++) { - if (a[i] === b[i]) { - c[i] = a[i]; - } - } - - return c; -} - - -/** - * Returns tuple length - * - * ```typescript - * import { Tuple } from 'tiinvo'; - * - * Tuple.length([1, 2, 3]); // 3 - * Tuple.length([1, 2, 3, 4, 5]); // 5 - * ``` - * - * @param a - * @returns - * @since 3.0.0 - */ -export const length = >(a: a) => a.length; - -/** - * Maps a tuple to a tuple using some functions `m[]`. - * - * ```typescript - * import { Tuple } from 'tiinvo'; - * - * const m = [ - * (a: number) => a + 1, - * (a: number) => a * 2, - * (a: number) => a * 3 - * ]; - * - * Tuple.map(m)([1, 2, 3]); // [2, 4, 6] - * ``` - * - * @param map - * @returns - * @since 3.0.0 - */ -export const map = any)[]>(... map: m) => >>(b: b): tuple> => { - const r = [] as unknown as tuple>; - for (let i = 0; i < b.length; i++) { - r.push(map[i](b[i])); - } - return r; -} - -/** - * Returns the union of two tuples `a` and `b`. - * - * ```typescript - * import { Tuple } from 'tiinvo'; - * - * Tuple.union([1, 2, 3])([1, 2, 3]); // [1, 2, 3] - * Tuple.union([1, 2, 3])([1, 2, 4]); // [1, 2, 3, 4] - * Tuple.union([1, 2, 3])([1, 2, 4, 5]); // [1, 2, 3, 4, 5] - * ``` - * - * @param a - * @returns - * @since 3.0.0 - */ -export const union = >(a: a) => >(b: b): a | b => - Array.from(new Set([...a, ...b])) as unknown as a | b; \ No newline at end of file diff --git a/tsconfig.commonjs.json b/tsconfig.commonjs.json deleted file mode 100644 index 76b1c60..0000000 --- a/tsconfig.commonjs.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "dist", - "module": "CommonJS", - "target": "ES2015" - } -} \ No newline at end of file