diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..03eefc7 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,5 @@ +[submodule "docs"] + path = docs + url = git@git.zakscode.com:ztimson/utils.wiki.git + branch = master + ignore = any diff --git a/README.md b/README.md new file mode 100644 index 0000000..a30754b --- /dev/null +++ b/README.md @@ -0,0 +1,90 @@ + +
+
+ + + Logo + + +### @ztimson/utils + + +Javascript/Typescript Utilities + + +[![Version](https://img.shields.io/badge/dynamic/json.svg?label=Version&style=for-the-badge&url=https://git.zakscode.com/api/v1/repos/ztimson/utils/tags&query=$[0].name)](https://git.zakscode.com/ztimson/utils/tags) +[![Pull Requests](https://img.shields.io/badge/dynamic/json.svg?label=Pull%20Requests&style=for-the-badge&url=https://git.zakscode.com/api/v1/repos/ztimson/utils&query=open_pr_counter)](https://git.zakscode.com/ztimson/utils/pulls) +[![Issues](https://img.shields.io/badge/dynamic/json.svg?label=Issues&style=for-the-badge&url=https://git.zakscode.com/api/v1/repos/ztimson/utils&query=open_issues_count)](https://git.zakscode.com/ztimson/utils/issues) + + + + --- +
+ Release Notes + • Report a Bug + • Request a Feature +
+ + --- +
+ +## Table of Contents +- [@ztimson/utils](#top) + - [About](#about) + - [Built With](#built-with) + - [Setup](#setup) + - [Production](#production) + - [Development](#development) + - [Documentation](https://git.zakscode.com/ztimson/utils/wiki) + - [License](#license) + +## About + +A collection of utilities to make life a little easier + +### Built With +[![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?style=for-the-badge&logo=typescript&logoColor=white)](https://typescriptlang.org/) + +## Setup + +
+ +

+ Production +

+
+ +#### Prerequisites +- [Node.js](https://nodejs.org/en/download) + +#### Instructions +1. Install persist: `npm i @ztimosn/utils` + +
+ +
+ +

+ Development +

+
+ +#### Prerequisites +- [Node.js](https://nodejs.org/en/download) + +#### Instructions +1. Install the dependencies: `npm i` +2. Build library: `npm build` +3. Run unit tests: `npm test` + +
+ +## Documentation + +[Available Here](https://git.zakscode.com/ztimson/utils/wiki) + +## License + +Copyright © 2023 Zakary Timson | Available under MIT Licensing + +See the [license](_media/LICENSE) for more information. diff --git a/docs b/docs new file mode 160000 index 0000000..dbf5d8c --- /dev/null +++ b/docs @@ -0,0 +1 @@ +Subproject commit dbf5d8cd076d8ddbf4e4b1b1e0c0c301270303dd diff --git a/package-lock.json b/package-lock.json index cf6fd2f..b51b27e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,20 @@ { "name": "@ztimson/utils", - "version": "0.15.3", + "version": "0.15.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@ztimson/utils", - "version": "0.15.3", + "version": "0.15.6", "license": "MIT", "devDependencies": { "@types/jest": "^29.5.12", "jest": "^29.7.0", "jest-junit": "^16.0.0", "ts-jest": "^29.1.2", + "typedoc": "^0.26.7", + "typedoc-plugin-markdown": "^4.2.7", "typescript": "^5.3.3", "vite": "^5.0.12", "vite-plugin-dts": "^3.7.2" @@ -1732,6 +1734,57 @@ "string-argv": "~0.3.1" } }, + "node_modules/@shikijs/core": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.18.0.tgz", + "integrity": "sha512-VK4BNVCd2leY62Nm2JjyxtRLkyrZT/tv104O81eyaCjHq4Adceq2uJVFJJAIof6lT1mBwZrEo2qT/T+grv3MQQ==", + "dev": true, + "dependencies": { + "@shikijs/engine-javascript": "1.18.0", + "@shikijs/engine-oniguruma": "1.18.0", + "@shikijs/types": "1.18.0", + "@shikijs/vscode-textmate": "^9.2.2", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.3" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-1.18.0.tgz", + "integrity": "sha512-qoP/aO/ATNwYAUw1YMdaip/YVEstMZEgrwhePm83Ll9OeQPuxDZd48szZR8oSQNQBT8m8UlWxZv8EA3lFuyI5A==", + "dev": true, + "dependencies": { + "@shikijs/types": "1.18.0", + "@shikijs/vscode-textmate": "^9.2.2", + "oniguruma-to-js": "0.4.3" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-1.18.0.tgz", + "integrity": "sha512-B9u0ZKI/cud+TcmF8Chyh+R4V5qQVvyDOqXC2l2a4x73PBSBc6sZ0JRAX3eqyJswqir6ktwApUUGBYePdKnMJg==", + "dev": true, + "dependencies": { + "@shikijs/types": "1.18.0", + "@shikijs/vscode-textmate": "^9.2.2" + } + }, + "node_modules/@shikijs/types": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-1.18.0.tgz", + "integrity": "sha512-O9N36UEaGGrxv1yUrN2nye7gDLG5Uq0/c1LyfmxsvzNPqlHzWo9DI0A4+fhW2y3bGKuQu/fwS7EPdKJJCowcVA==", + "dev": true, + "dependencies": { + "@shikijs/vscode-textmate": "^9.2.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-9.2.2.tgz", + "integrity": "sha512-TMp15K+GGYrWlZM8+Lnj9EaHEFmOen0WJBrfa17hF7taDOYthuPPV0GWzfd/9iMij0akS/8Yw2ikquH7uVi/fg==", + "dev": true + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -1818,6 +1871,15 @@ "@types/node": "*" } }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dev": true, + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -1852,6 +1914,15 @@ "pretty-format": "^29.0.0" } }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "dev": true, + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/node": { "version": "18.19.14", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.14.tgz", @@ -1867,6 +1938,12 @@ "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", "dev": true }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "dev": true + }, "node_modules/@types/yargs": { "version": "17.0.32", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", @@ -1882,6 +1959,12 @@ "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", "dev": true }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, "node_modules/@volar/language-core": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-1.11.1.tgz", @@ -2281,6 +2364,16 @@ } ] }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -2306,6 +2399,26 @@ "node": ">=10" } }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/ci-info": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", @@ -2384,6 +2497,16 @@ "node": ">=0.1.90" } }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/commander": { "version": "9.5.0", "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", @@ -2493,6 +2616,15 @@ "node": ">=0.10.0" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -2502,6 +2634,19 @@ "node": ">=8" } }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dev": true, + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/diff-sequences": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", @@ -2885,6 +3030,42 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-to-html": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.3.tgz", + "integrity": "sha512-M17uBDzMJ9RPCqLMO92gNNUDuBSq10a25SDBI08iCCxmorf4Yy6sYHK57n9WAbRAAaU+DuR4W6GN9K4DFZesYg==", + "dev": true, + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "dev": true, + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -2900,6 +3081,16 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -3761,6 +3952,15 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "dev": true, + "dependencies": { + "uc.micro": "^2.0.0" + } + }, "node_modules/locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -3809,6 +4009,12 @@ "node": ">=10" } }, + "node_modules/lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", + "dev": true + }, "node_modules/make-dir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", @@ -3839,12 +4045,151 @@ "tmpl": "1.0.5" } }, + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/markdown-it/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "dev": true, + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "dev": true + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", "dev": true }, + "node_modules/micromark-util-character": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.0.tgz", + "integrity": "sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.0.tgz", + "integrity": "sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", + "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.0.tgz", + "integrity": "sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, "node_modules/micromatch": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", @@ -3868,9 +4213,9 @@ } }, "node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -3987,6 +4332,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/oniguruma-to-js": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/oniguruma-to-js/-/oniguruma-to-js-0.4.3.tgz", + "integrity": "sha512-X0jWUcAlxORhOqqBREgPMgnshB7ZGYszBNspP+tS9hPD3l13CdaXcHbgImoHUHlrvGx/7AvFEkTRhAGYh+jzjQ==", + "dev": true, + "dependencies": { + "regex": "^4.3.2" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -4201,6 +4558,16 @@ "node": ">= 6" } }, + "node_modules/property-information": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", + "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -4210,6 +4577,15 @@ "node": ">=6" } }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/pure-rand": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.4.tgz", @@ -4232,6 +4608,12 @@ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true }, + "node_modules/regex": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/regex/-/regex-4.3.2.tgz", + "integrity": "sha512-kK/AA3A9K6q2js89+VMymcboLOlF5lZRCYJv3gzszXFHBr6kO6qLGzbm+UIugBEV8SMMKCTR59txoY6ctRHYVw==", + "dev": true + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -4358,6 +4740,20 @@ "node": ">=8" } }, + "node_modules/shiki": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.18.0.tgz", + "integrity": "sha512-8jo7tOXr96h9PBQmOHVrltnETn1honZZY76YA79MHheGQg55jBvbm9dtU+MI5pjC5NJCFuA6rvVTLVeSW5cE4A==", + "dev": true, + "dependencies": { + "@shikijs/core": "1.18.0", + "@shikijs/engine-javascript": "1.18.0", + "@shikijs/engine-oniguruma": "1.18.0", + "@shikijs/types": "1.18.0", + "@shikijs/vscode-textmate": "^9.2.2", + "@types/hast": "^3.0.4" + } + }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -4407,6 +4803,16 @@ "source-map": "^0.6.0" } }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -4461,6 +4867,20 @@ "node": ">=8" } }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "dev": true, + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -4590,6 +5010,16 @@ "node": ">=8.0" } }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/ts-jest": { "version": "29.1.2", "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.2.tgz", @@ -4654,6 +5084,40 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/typedoc": { + "version": "0.26.7", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.26.7.tgz", + "integrity": "sha512-gUeI/Wk99vjXXMi8kanwzyhmeFEGv1LTdTQsiyIsmSYsBebvFxhbcyAx7Zjo4cMbpLGxM4Uz3jVIjksu/I2v6Q==", + "dev": true, + "dependencies": { + "lunr": "^2.3.9", + "markdown-it": "^14.1.0", + "minimatch": "^9.0.5", + "shiki": "^1.16.2", + "yaml": "^2.5.1" + }, + "bin": { + "typedoc": "bin/typedoc" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x" + } + }, + "node_modules/typedoc-plugin-markdown": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-4.2.7.tgz", + "integrity": "sha512-bLsQdweSm48P9j6kGqQ3/4GCH5zu2EnURSkkxqirNc+uVFE9YK825ogDw+WbNkRHIV6eZK/1U43gT7YfglyYOg==", + "dev": true, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "typedoc": "0.26.x" + } + }, "node_modules/typescript": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", @@ -4667,12 +5131,86 @@ "node": ">=14.17" } }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true + }, "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", "dev": true }, + "node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "dev": true, + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "dev": true, + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dev": true, + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "dev": true, + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "dev": true, + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -4753,6 +5291,34 @@ "node": ">= 0.10" } }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "dev": true, + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "dev": true, + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/vite": { "version": "5.2.8", "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.8.tgz", @@ -4942,6 +5508,18 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, + "node_modules/yaml": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz", + "integrity": "sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==", + "dev": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", @@ -5000,6 +5578,16 @@ "optionalDependencies": { "commander": "^9.4.1" } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } } } } diff --git a/package.json b/package.json index 609dcdb..ffab507 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ztimson/utils", - "version": "0.15.6", + "version": "0.16.0", "description": "Utility library", "author": "Zak Timson", "license": "MIT", @@ -21,6 +21,7 @@ }, "scripts": { "build": "npx tsc && npx vite build", + "docs": "typedoc --plugin typedoc-plugin-markdown --hidePageHeader --out ./docs --entryPoints src/**/*.ts", "test": "npx jest", "test:coverage": "npx jest --coverage", "watch": "npx vite build --watch" @@ -30,6 +31,8 @@ "jest": "^29.7.0", "jest-junit": "^16.0.0", "ts-jest": "^29.1.2", + "typedoc": "^0.26.7", + "typedoc-plugin-markdown": "^4.2.7", "typescript": "^5.3.3", "vite": "^5.0.12", "vite-plugin-dts": "^3.7.2" diff --git a/src/array.ts b/src/array.ts index a0dea1c..09247b8 100644 --- a/src/array.ts +++ b/src/array.ts @@ -1,10 +1,32 @@ import {dotNotation, isEqual} from './objects'; +/** + * Only add element to array if it isn't already included + * + * @example + * ```js + * const arr = addUnique([1, 2, 3], 3); + * console.log(arr); // Output: [1, 2, 3] + * ``` + * + * @param {T[]} array Target array element will be added to + * @param {T} el Unique element to add + * @return {T[]} Array with element if it was unique + * @deprecated Use ASet to create unique arrays + */ export function addUnique(array: T[], el: T): T[] { if(array.indexOf(el) === -1) array.push(el); return array; } +/** + * Find all unique elements in arrays + * + * @param {any[]} a First array to compare + * @param {any[]} b Second array to compare + * @return {any[]} Unique elements + * @deprecated Use ASet to perform Set operations on arrays + */ export function arrayDiff(a: any[], b: any[]): any[] { return makeUnique([ ...a.filter(v1 => !b.includes((v2: any) => isEqual(v1, v2))), @@ -33,6 +55,25 @@ export function caseInsensitiveSort(prop: string) { }; } +/** + * Shorthand to find objects with a property value + * + * @example + * ```js + * const found = [ + * {name: 'Batman'}, + * {name: 'Superman'}, + * ].filter(findByProp('name', 'Batman')); + * ``` + * + * @param {string} prop Property to compare (Dot nation supported) + * @param value Value property must have + * @return {(v: any) => boolean} Function used by `filter` or `find` + */ +export function findByProp(prop: string, value: any) { + return (v: any) => isEqual(dotNotation(v, prop), value); +} + /** * Recursively flatten nested arrays * @@ -91,10 +132,13 @@ export function sortByProp(prop: string, reverse = false) { }; } -export function findByProp(prop: string, value: any) { - return (v: any) => isEqual(v[prop], value); -} - +/** + * Make sure every element in array is unique + * + * @param {any[]} arr Array that will be filtered in place + * @return {any[]} Original array + * @deprecated Please use ASet to create a guaranteed unique array + */ export function makeUnique(arr: any[]) { for(let i = arr.length - 1; i >= 0; i--) { if(arr.slice(0, i).find(n => isEqual(n, arr[i]))) arr.splice(i, 1); @@ -103,7 +147,8 @@ export function makeUnique(arr: any[]) { } /** - * Make sure value is an array, if it isn't wrap it in one. + * Make sure value is an array, if it isn't wrap it in one + * * @param {T[] | T} value Value that should be an array * @returns {T[]} Value in an array */ diff --git a/src/files.ts b/src/files.ts index 314d52b..72f058f 100644 --- a/src/files.ts +++ b/src/files.ts @@ -1,21 +1,39 @@ -import {deepCopy, JSONAttemptParse} from './objects.ts'; +import {JSONAttemptParse} from './objects.ts'; import {PromiseProgress} from './promise-progress'; -export function download(href: any, name: string) { +/** + * Download a file from a URL + * + * @param href URL that will be downloaded + * @param {string} name Override download name + */ +export function download(href: any, name?: string) { const a = document.createElement('a'); a.href = href; - a.download = name; + a.download = name || href.split('/').pop(); document.body.appendChild(a); a.click(); document.body.removeChild(a); } +/** + * Download blob as a file + * + * @param {Blob} blob File as a blob + * @param {string} name Name blob will be downloaded as + */ export function downloadBlob(blob: Blob, name: string) { const url = URL.createObjectURL(blob); download(url, name); URL.revokeObjectURL(url); } +/** + * Open filebrowser & return selected file + * + * @param {{accept?: string, multiple?: boolean}} options accept - selectable mimetypes, multiple - Allow selecting more than 1 file + * @return {Promise} Array of selected files + */ export function fileBrowser(options: {accept?: string, multiple?: boolean} = {}): Promise { return new Promise(res => { const input = document.createElement('input'); @@ -32,6 +50,25 @@ export function fileBrowser(options: {accept?: string, multiple?: boolean} = {}) }); } +/** + * Create timestamp intended for filenames from a date + * + * @param {string} name Name of file, `{{TIMESTAMP}}` will be replaced + * @param {Date | number | string} date Date to use for timestamp + * @return {string} Interpolated filename, or the raw timestamp if name was omitted + */ +export function timestampFilename(name?: string, date: Date | number | string = new Date()) { + if(typeof date == 'number' || typeof date == 'string') date = new Date(date); + const timestamp = `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}_${date.getHours().toString().padStart(2, '0')}-${date.getMinutes().toString().padStart(2, '0')}-${date.getSeconds().toString().padStart(2, '0')}`; + return name ? name.replace('{{TIMESTAMP}}', timestamp) : timestamp; +} + +/** + * Upload file to URL with progress callback using PromiseProgress + * + * @param {{url: string, files: File[], headers?: {[p: string]: string}, withCredentials?: boolean}} options + * @return {PromiseProgress} Promise of request with `onProgress` callback + */ export function uploadWithProgress(options: { url: string; files: File[]; diff --git a/src/index.ts b/src/index.ts index 2f1b8f1..2d10954 100644 --- a/src/index.ts +++ b/src/index.ts @@ -11,3 +11,4 @@ export * from './objects'; export * from './promise-progress'; export * from './string'; export * from './time'; +export * from './types'; diff --git a/src/misc.ts b/src/misc.ts index f2ab3cb..3284c04 100644 --- a/src/misc.ts +++ b/src/misc.ts @@ -11,42 +11,3 @@ export function gravatar(email: string, def='mp') { if(!email) return ''; return `https://www.gravatar.com/avatar/${md5(email)}?d=${def}`; } - -/** Parts of a URL */ -export type ParsedUrl = { - protocol?: string, - subdomain?: string, - domain: string, - host: string, - port?: number, - path?: string, - query?: {[name: string]: string} - fragment?: string -} - -/** - * - * @param {string} url - * @returns {RegExpExecArray} - */ -export function urlParser(url: string): ParsedUrl { - const processed = new RegExp( - '(?:(?[\\w\\d]+)\\:\\/\\/)?(?:(?.+)\\@)?(?(?[^:\\/\\?#@\\n]+)(?:\\:(?\\d*))?)(?\\/.*?)?(?:\\?(?.*?))?(?:#(?.*?))?$', - 'gm').exec(url); - const groups: ParsedUrl = processed?.groups ?? {}; - const domains = groups.domain.split('.'); - if(groups['port'] != null) groups.port = Number(groups.port); - if(domains.length > 2) { - groups.domain = domains.splice(-2, 2).join('.'); - groups.subdomain = domains.join('.'); - } - if(groups.query) { - const split = (groups.query).split('&'), query: any = {}; - split.forEach((q: any) => { - const [key, val] = q.split('='); - query[key] = val; - }); - groups.query = query; - } - return groups; -} diff --git a/src/objects.ts b/src/objects.ts index 8a67294..c411fd4 100644 --- a/src/objects.ts +++ b/src/objects.ts @@ -1,5 +1,5 @@ /** - * Removes any null values from an object in-place + * Removes any null values from an object in-place * * @example * ```ts @@ -27,12 +27,12 @@ export function clean(obj: T, undefinedOnly = false): Partial { * Create a deep copy of an object (vs. a shallow copy of references) * * Should be replaced by `structuredClone` once released. - * * @param {T} value Object to copy * @returns {T} Type + * @deprecated Please use `structuredClone` */ export function deepCopy(value: T): T { - return JSON.parse(JSON.stringify(value)); + return structuredClone(value); } /** @@ -91,8 +91,28 @@ export function dotNotation(obj: any, prop: string, set?: T): T | undefined { }, obj); } + /** - * Recursively flatten a nested object, while maintaining key structure. + * Convert object into URL encoded query string + * + * @example + * ```js + * const query = encodeQuery({page: 1, size: 20}); + * console.log(query); // Output: "page=1&size=20" + * ``` + * + * @param {any} data - data to convert + * @returns {string} - Encoded form data + */ +export function encodeQuery(data: any): string { + return Object.entries(data).map(([key, value]) => + encodeURIComponent(key) + '=' + encodeURIComponent(value) + ).join('&'); +} + + +/** + * Recursively flatten a nested object, while maintaining key structure * * @example * ```ts @@ -121,6 +141,7 @@ export function flattenObj(obj: any, parent?: any, result: any = {}) { /** * Convert object to FormData + * * @param target - Object to convert * @return {FormData} - Form object */ @@ -173,6 +194,12 @@ export function isEqual(a: any, b: any): boolean { return Object.keys(a).every(key => isEqual(a[key], b[key])); } +/** + * Experimental: Combine multiple object prototypes into one + * + * @param target Object that will have prototypes added + * @param {any[]} constructors Additionally prototypes that should be merged into target + */ export function mixin(target: any, constructors: any[]) { constructors.forEach(c => { Object.getOwnPropertyNames(c.prototype).forEach((name) => { @@ -186,30 +213,31 @@ export function mixin(target: any, constructors: any[]) { }); } +/** + * Parse JSON but return the original string if it fails + * + * @param {string} json JSON string to parse + * @return {string | T} Object if successful, original string otherwise + */ export function JSONAttemptParse(json: string): T | string { try { return JSON.parse(json); } catch { return json; } } -export function JSONSanitized(obj: any, space?: number) { +/** + * Convert an object to a JSON string avoiding any circular references. + * + * @param obj Object to convert to JSON + * @param {number} space Format the JSON with spaces + * @return {string} JSON string + */ +export function JSONSanitize(obj: any, space?: number): string { let cache: any[] = []; - return JSON.parse(JSON.stringify(obj, (key, value) => { + return JSON.stringify(obj, (key, value) => { if (typeof value === 'object' && value !== null) { if (cache.includes(value)) return; cache.push(value); } return value; - }, space)); -} - -/** - * Convert object into URL encoded string - * - * @param {any} data - data to convert - * @returns {string} - Encoded form data - */ -export function urlEncode(data: any): string { - return Object.entries(data).map(([key, value]) => - encodeURIComponent(key) + '=' + encodeURIComponent(value) - ).join('&'); + }, space); } diff --git a/src/promise-progress.ts b/src/promise-progress.ts index 1f41e7d..13076fa 100644 --- a/src/promise-progress.ts +++ b/src/promise-progress.ts @@ -1,5 +1,25 @@ export type ProgressCallback = (progress: number) => any; +/** + * A promise that fires the `onProgress` callback on incremental progress + * + * @example + * ```js + * const promise = new Promise((resolve, reject, progress) => { + * const max = 10; + * for(let i = 0; i < max; i++) progress(i / max); + * resolve(1); + * }); + * + * console.log(promise.progress); + * + * promise.onProgress(console.log) + * .then(console.log) + * .catch(console.error) + * .finally(...); + * + * ``` + */ export class PromiseProgress extends Promise { private listeners: ProgressCallback[] = []; diff --git a/src/string.ts b/src/string.ts index e5f64af..9069ee0 100644 --- a/src/string.ts +++ b/src/string.ts @@ -1,28 +1,10 @@ -export function countChars(text: string, pattern: RegExp) { - return text.length - text.replaceAll(pattern, '').length; -} - -export function createHex(length: number) { - return Array(length).fill(null).map(() => Math.round(Math.random() * 0xF).toString(16)).join(''); -} - -export function formatBytes(bytes: number, decimals = 2) { - if (bytes === 0) return '0 Bytes'; - const k = 1024; - const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; - const i = Math.floor(Math.log(bytes) / Math.log(k)); - return parseFloat((bytes / Math.pow(k, i)).toFixed(decimals)) + ' ' + sizes[i]; -} - /** * String of all letters - * */ const LETTER_LIST = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; /** * String of all numbers - * */ const NUMBER_LIST = '0123456789'; @@ -36,6 +18,37 @@ const SYMBOL_LIST = '~`!@#$%^&*()_-+={[}]|\\:;"\'<,>.?/'; */ const CHAR_LIST = LETTER_LIST + NUMBER_LIST + SYMBOL_LIST; +/** + * Generate a random hexadecimal value + * + * @param {number} length Number of hexadecimal place values + * @return {string} Hexadecimal number as a string + */ +export function randomHex(length: number) { + return Array(length).fill(null).map(() => Math.round(Math.random() * 0xF).toString(16)).join(''); +} + +/** + * Convert number of bytes into a human-readable size + * + * @param {number} bytes Number of bytes + * @param {number} decimals Decimal places to preserve + * @return {string} Formated size + */ +export function formatBytes(bytes: number, decimals = 2) { + if (bytes === 0) return '0 Bytes'; + const k = 1024; + const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(decimals)) + ' ' + sizes[i]; +} + +/** + * Extract numbers from a string & create a formated phone number: +1 (123) 456-7890 + * + * @param {string} number String that will be parsed for numbers + * @return {string} Formated phone number + */ export function formatPhoneNumber(number: string) { const parts = /(\+?1)?.*?(\d{3}).*?(\d{3}).*?(\d{4})/g.exec(number); if(!parts) throw new Error(`Number cannot be parsed: ${number}`); @@ -46,7 +59,7 @@ export function formatPhoneNumber(number: string) { * Insert a string into another string at a given position * * @example - * ``` + * ```js * console.log(insertAt('Hello world!', ' glorious', 5); * // Output: Hello glorious world! * ``` @@ -60,12 +73,26 @@ export function insertAt(target: string, str: string, index: number): String { return `${target.slice(0, index)}${str}${target.slice(index + 1)}`; } -export function pad(text: any, length: number, char: string, start = true) { - const t = text.toString(); - const l = length - t.length; - if(l <= 0) return t; - const padding = Array(~~(l / char.length)).fill(char).join(''); - return start ? padding + t : t + padding; +/** + * Add padding to string + * + * @example + * ```js + * const now = new Date(); + * const padded = now.getHours() + ':' + pad(now.getMinutes(), 2, '0'); + * console.log(padded); // Output: "2:05" + * ``` + * + * @param text Text that will be padded + * @param {number} length Target length + * @param {string} char Character to use as padding, defaults to space + * @param {boolean} start Will pad start of text if true, or the end if false + * @return {string} Padded string + * @deprecated Please use `String.padStart` & `String.padEnd` + */ +export function pad(text: any, length: number, char: string = ' ', start = true) { + if(start) return text.toString().padStart(length, char); + return text.toString().padEnd(length, char); } /** @@ -149,8 +176,50 @@ export function matchAll(value: string, regex: RegExp | string): RegExpExecArray return ret; } +/** Parts of a URL */ +export type ParsedUrl = { + protocol?: string, + subdomain?: string, + domain: string, + host: string, + port?: number, + path?: string, + query?: {[name: string]: string} + fragment?: string +} + +/** + * Break a URL string into its parts for easy parsing + * + * @param {string} url URL string that will be parsed + * @returns {RegExpExecArray} Parts of URL + */ +export function parseUrl(url: string): ParsedUrl { + const processed = new RegExp( + '(?:(?[\\w\\d]+)\\:\\/\\/)?(?:(?.+)\\@)?(?(?[^:\\/\\?#@\\n]+)(?:\\:(?\\d*))?)(?\\/.*?)?(?:\\?(?.*?))?(?:#(?.*?))?$', + 'gm').exec(url); + const groups: ParsedUrl = processed?.groups ?? {}; + const domains = groups.domain.split('.'); + if(groups['port'] != null) groups.port = Number(groups.port); + if(domains.length > 2) { + groups.domain = domains.splice(-2, 2).join('.'); + groups.subdomain = domains.join('.'); + } + if(groups.query) { + const split = (groups.query).split('&'), query: any = {}; + split.forEach((q: any) => { + const [key, val] = q.split('='); + query[key] = val; + }); + groups.query = query; + } + return groups; +} + + /** * Create MD5 hash using native javascript + * * @param d String to hash * @returns {string} Hashed string */ diff --git a/src/time.ts b/src/time.ts index 10994d2..8b99f47 100644 --- a/src/time.ts +++ b/src/time.ts @@ -1,13 +1,17 @@ -export function formatDate(date: Date | number | string) { - const d = date instanceof Date ? date : new Date(date); - return new Intl.DateTimeFormat("en-us", { - weekday: "long", - month: "short", - day: "numeric", - hour: "numeric", - minute: "numeric", - hour12: true - }).format(d); +/** + * Return date formated highest to lowest: YYYY-MM-DD H:mm AM + * + * @param {Date | number | string} date Date or timestamp to convert to string + * @return {string} Formated date + */ +export function formatDate(date: Date | number | string): string { + if(typeof date == 'number' || typeof date == 'string') date = new Date(date); + let hours = date.getHours(), postfix = 'AM'; + if(hours >= 12) { + if(hours > 12) hours -= 12; + postfix = 'PM'; + } else if(hours == 0) hours = 12; + return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}, ${hours}:${date.getMinutes().toString().padStart(2, '0')} ${postfix}`; } /** @@ -17,6 +21,7 @@ export function formatDate(date: Date | number | string) { * ```js * await sleep(1000) // Pause for 1 second * ``` + * * @param {number} ms - Time to pause for in milliseconds * @returns {Promise} - Resolves promise when it's time to resume */ @@ -33,6 +38,7 @@ export function sleep(ms: number): Promise { * setTimeout(() => wait = false, 1000); * await sleepUntil(() => loading); // Won't continue until loading flag is false * ``` + * * @param {() => boolean} fn Return true to continue * @param {number} checkInterval Run function ever x milliseconds * @return {Promise} Callback when sleep is over diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..4ae1f63 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,20 @@ +/** + * Return keys on a type as an array of strings + * + * @example + * ```ts + * type Person = { + * firstName: string; + * lastName: string; + * age: number; + * } + * + * const keys = typeKeys(); + * console.log(keys); // Output: ["firstName", "lastName", "age"] + * ``` + * + * @return {Array} Available keys + */ +export function tyoeKeys() { + return Object.keys({}) as Array; +} diff --git a/tests/misc.spec.ts b/tests/misc.spec.ts index d2b5b8d..7a832ba 100644 --- a/tests/misc.spec.ts +++ b/tests/misc.spec.ts @@ -1,4 +1,4 @@ -import {sleep, urlParser} from '../src'; +import {sleep, parseUrl} from '../src'; describe('Miscellanies Utilities', () => { describe('sleep', () => { @@ -12,7 +12,7 @@ describe('Miscellanies Utilities', () => { describe('urlParser', () => { test('localhost w/ port', () => { - const parsed = urlParser('http://localhost:4200/some/path?q1=test1&q2=test2#frag'); + const parsed = parseUrl('http://localhost:4200/some/path?q1=test1&q2=test2#frag'); expect(parsed.protocol).toStrictEqual('http'); expect(parsed.host).toStrictEqual('localhost:4200'); expect(parsed.domain).toStrictEqual('localhost'); @@ -23,7 +23,7 @@ describe('Miscellanies Utilities', () => { }); test('advanced URL', () => { - const parsed = urlParser('https://sub.domain.example.com/some/path?q1=test1&q2=test2#frag'); + const parsed = parseUrl('https://sub.domain.example.com/some/path?q1=test1&q2=test2#frag'); expect(parsed.protocol).toStrictEqual('https'); expect(parsed.host).toStrictEqual('sub.domain.example.com'); expect(parsed.domain).toStrictEqual('example.com');