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 @@
+
+
+
+
+
+
+
+
+### @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)
+
+
+
+ ---
+
+
+ ---
+
+
+## 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');