├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── src └── index.js ├── test.html └── test └── nanosearch.test.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | 3 | node_modules 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 2.0.0 4 | 5 | This release is backward-incompatible with `1.0.0`! 6 | 7 | ### Upgrading 8 | 9 | The easiest path to upgrading is simply to `engine.clear()`, then re-index all 10 | of your documents. This is only needed if you're persisting your index. 11 | 12 | > _Note:_ Alternatively, you can manually change the top-level 13 | > `engine.index["version"]` key to `"2.0.0"`, though you need to be careful 14 | > when messing with internals. 15 | 16 | You'll also need to check your usages of `SearchEngine.search`, as its API has 17 | changed. The `start` & `limit` parameters were dropped, but can be applied to 18 | `Results.slice(start, end)` instead for an identical result. 19 | 20 | ### Changes 21 | 22 | * Changed the `SearchEngine.search` method to return a `Results` object instead 23 | of a plain array. This object allows for getting the total result count, 24 | accessing a specific offset within the results, iterating over all results, & 25 | a slicing interface to the result set. 26 | * Added an `EnglishPreprocessor`. This comes with an English-specific set of 27 | stop words (common words to filter out that would otherwise dilute the 28 | search results). We also export the `ENGLISH_STOP_WORDS` constant for your 29 | use. 30 | * Added a `RegExpTokenizer`. This allows for stemming based on a regular 31 | expression, which can be useful for simple or non-English situations. 32 | 33 | 34 | ## 1.0.0 35 | 36 | Initial release. 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2022 Daniel Lindsley 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, 7 | this list of conditions and the following disclaimer. 8 | 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | 3. Neither the name of the copyright holder nor the names of its contributors 14 | may be used to endorse or promote products derived from this software without 15 | specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 21 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 | POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `nanosearch` 2 | 3 | A tiny search engine. 4 | 5 | Suitable for in-browser use, this provides n-gram based search results. 6 | 7 | 8 | ## Quickstart 9 | 10 | ```js 11 | import { SearchEngine } from '@toastdriven/nanosearch'; 12 | 13 | // Create a search engine. 14 | const engine = new SearchEngine(); 15 | 16 | // Index some documents. 17 | // First parameter is the unique document ID, second is the document text. 18 | engine.add("abc", "The dog is a 'hot dog'."); 19 | engine.add("def", "Dogs > Cats"); 20 | engine.add("ghi", "the quick brown fox jumps over the lazy dog"); 21 | engine.add("jkl", "Am I lazy, or just work smart?"); 22 | 23 | // Then, you can let the user search on the engine... 24 | let myDogResults = engine.search("my dog"); 25 | myDogResults.count(); // 3 26 | 27 | for(let res of myDogResults.iterator()) { 28 | console.log(res.docId); // ex: "def" 29 | console.log(res.score); // ex: 0.2727272727272727 30 | } 31 | 32 | // ...including limiting results (to just one)... 33 | let lazyResults = engine.search("lazy"); 34 | let topResult = lazyResults.at(0); 35 | console.log(topResult); 36 | 37 | // ...or making pages of ten results! 38 | let dogResults = engine.search("dogs"); 39 | let pageOne = dogResults.slice(0, 10); 40 | let pageTwo = dogResults.slice(10, 20); 41 | console.log(pageOne); 42 | console.log(pageTwo); 43 | ``` 44 | 45 | 46 | ## Installation 47 | 48 | `$ npm install @toastdriven/nanosearch` 49 | 50 | 51 | ## Requirements 52 | 53 | * ES6 (or similar translation/polyfill) 54 | 55 | 56 | ## Tests 57 | 58 | ```shell 59 | $ git clone git@github.com:toastdriven/nanosearch.git 60 | $ cd nanosearch 61 | $ npm install 62 | $ npm test 63 | ``` 64 | 65 | 66 | ## Docs 67 | 68 | ```shell 69 | $ git clone git@github.com:toastdriven/nanosearch.git 70 | $ cd nanosearch 71 | $ npm install 72 | $ ./node_modules/.bin/jsdoc -r -d ~/Desktop/out --package package.json --readme README.md src 73 | ``` 74 | 75 | 76 | ## License 77 | 78 | New BSD 79 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nanosearch", 3 | "version": "1.0.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "nanosearch", 9 | "version": "1.0.0", 10 | "license": "BSD-3-Clause", 11 | "devDependencies": { 12 | "jsdoc": "^3.6.11", 13 | "mocha": "^9.1.3" 14 | } 15 | }, 16 | "node_modules/@babel/parser": { 17 | "version": "7.18.9", 18 | "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.9.tgz", 19 | "integrity": "sha512-9uJveS9eY9DJ0t64YbIBZICtJy8a5QrDEVdiLCG97fVLpDTpGX7t8mMSb6OWw6Lrnjqj4O8zwjELX3dhoMgiBg==", 20 | "dev": true, 21 | "bin": { 22 | "parser": "bin/babel-parser.js" 23 | }, 24 | "engines": { 25 | "node": ">=6.0.0" 26 | } 27 | }, 28 | "node_modules/@types/linkify-it": { 29 | "version": "3.0.2", 30 | "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.2.tgz", 31 | "integrity": "sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==", 32 | "dev": true 33 | }, 34 | "node_modules/@types/markdown-it": { 35 | "version": "12.2.3", 36 | "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", 37 | "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", 38 | "dev": true, 39 | "dependencies": { 40 | "@types/linkify-it": "*", 41 | "@types/mdurl": "*" 42 | } 43 | }, 44 | "node_modules/@types/mdurl": { 45 | "version": "1.0.2", 46 | "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz", 47 | "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==", 48 | "dev": true 49 | }, 50 | "node_modules/@ungap/promise-all-settled": { 51 | "version": "1.1.2", 52 | "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", 53 | "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", 54 | "dev": true 55 | }, 56 | "node_modules/ansi-colors": { 57 | "version": "4.1.1", 58 | "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", 59 | "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", 60 | "dev": true, 61 | "engines": { 62 | "node": ">=6" 63 | } 64 | }, 65 | "node_modules/ansi-regex": { 66 | "version": "5.0.1", 67 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 68 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 69 | "dev": true, 70 | "engines": { 71 | "node": ">=8" 72 | } 73 | }, 74 | "node_modules/ansi-styles": { 75 | "version": "4.3.0", 76 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 77 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 78 | "dev": true, 79 | "dependencies": { 80 | "color-convert": "^2.0.1" 81 | }, 82 | "engines": { 83 | "node": ">=8" 84 | }, 85 | "funding": { 86 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 87 | } 88 | }, 89 | "node_modules/anymatch": { 90 | "version": "3.1.2", 91 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", 92 | "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", 93 | "dev": true, 94 | "dependencies": { 95 | "normalize-path": "^3.0.0", 96 | "picomatch": "^2.0.4" 97 | }, 98 | "engines": { 99 | "node": ">= 8" 100 | } 101 | }, 102 | "node_modules/argparse": { 103 | "version": "2.0.1", 104 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", 105 | "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", 106 | "dev": true 107 | }, 108 | "node_modules/balanced-match": { 109 | "version": "1.0.2", 110 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 111 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 112 | "dev": true 113 | }, 114 | "node_modules/binary-extensions": { 115 | "version": "2.2.0", 116 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", 117 | "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", 118 | "dev": true, 119 | "engines": { 120 | "node": ">=8" 121 | } 122 | }, 123 | "node_modules/bluebird": { 124 | "version": "3.7.2", 125 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", 126 | "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", 127 | "dev": true 128 | }, 129 | "node_modules/brace-expansion": { 130 | "version": "1.1.11", 131 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 132 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 133 | "dev": true, 134 | "dependencies": { 135 | "balanced-match": "^1.0.0", 136 | "concat-map": "0.0.1" 137 | } 138 | }, 139 | "node_modules/braces": { 140 | "version": "3.0.2", 141 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", 142 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", 143 | "dev": true, 144 | "dependencies": { 145 | "fill-range": "^7.0.1" 146 | }, 147 | "engines": { 148 | "node": ">=8" 149 | } 150 | }, 151 | "node_modules/browser-stdout": { 152 | "version": "1.3.1", 153 | "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", 154 | "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", 155 | "dev": true 156 | }, 157 | "node_modules/camelcase": { 158 | "version": "6.3.0", 159 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", 160 | "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", 161 | "dev": true, 162 | "engines": { 163 | "node": ">=10" 164 | }, 165 | "funding": { 166 | "url": "https://github.com/sponsors/sindresorhus" 167 | } 168 | }, 169 | "node_modules/catharsis": { 170 | "version": "0.9.0", 171 | "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", 172 | "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", 173 | "dev": true, 174 | "dependencies": { 175 | "lodash": "^4.17.15" 176 | }, 177 | "engines": { 178 | "node": ">= 10" 179 | } 180 | }, 181 | "node_modules/chalk": { 182 | "version": "4.1.2", 183 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 184 | "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 185 | "dev": true, 186 | "dependencies": { 187 | "ansi-styles": "^4.1.0", 188 | "supports-color": "^7.1.0" 189 | }, 190 | "engines": { 191 | "node": ">=10" 192 | }, 193 | "funding": { 194 | "url": "https://github.com/chalk/chalk?sponsor=1" 195 | } 196 | }, 197 | "node_modules/chalk/node_modules/supports-color": { 198 | "version": "7.2.0", 199 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 200 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 201 | "dev": true, 202 | "dependencies": { 203 | "has-flag": "^4.0.0" 204 | }, 205 | "engines": { 206 | "node": ">=8" 207 | } 208 | }, 209 | "node_modules/chokidar": { 210 | "version": "3.5.3", 211 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", 212 | "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", 213 | "dev": true, 214 | "funding": [ 215 | { 216 | "type": "individual", 217 | "url": "https://paulmillr.com/funding/" 218 | } 219 | ], 220 | "dependencies": { 221 | "anymatch": "~3.1.2", 222 | "braces": "~3.0.2", 223 | "glob-parent": "~5.1.2", 224 | "is-binary-path": "~2.1.0", 225 | "is-glob": "~4.0.1", 226 | "normalize-path": "~3.0.0", 227 | "readdirp": "~3.6.0" 228 | }, 229 | "engines": { 230 | "node": ">= 8.10.0" 231 | }, 232 | "optionalDependencies": { 233 | "fsevents": "~2.3.2" 234 | } 235 | }, 236 | "node_modules/cliui": { 237 | "version": "7.0.4", 238 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", 239 | "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", 240 | "dev": true, 241 | "dependencies": { 242 | "string-width": "^4.2.0", 243 | "strip-ansi": "^6.0.0", 244 | "wrap-ansi": "^7.0.0" 245 | } 246 | }, 247 | "node_modules/color-convert": { 248 | "version": "2.0.1", 249 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 250 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 251 | "dev": true, 252 | "dependencies": { 253 | "color-name": "~1.1.4" 254 | }, 255 | "engines": { 256 | "node": ">=7.0.0" 257 | } 258 | }, 259 | "node_modules/color-name": { 260 | "version": "1.1.4", 261 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 262 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 263 | "dev": true 264 | }, 265 | "node_modules/concat-map": { 266 | "version": "0.0.1", 267 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 268 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", 269 | "dev": true 270 | }, 271 | "node_modules/debug": { 272 | "version": "4.3.3", 273 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", 274 | "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", 275 | "dev": true, 276 | "dependencies": { 277 | "ms": "2.1.2" 278 | }, 279 | "engines": { 280 | "node": ">=6.0" 281 | }, 282 | "peerDependenciesMeta": { 283 | "supports-color": { 284 | "optional": true 285 | } 286 | } 287 | }, 288 | "node_modules/debug/node_modules/ms": { 289 | "version": "2.1.2", 290 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 291 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 292 | "dev": true 293 | }, 294 | "node_modules/decamelize": { 295 | "version": "4.0.0", 296 | "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", 297 | "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", 298 | "dev": true, 299 | "engines": { 300 | "node": ">=10" 301 | }, 302 | "funding": { 303 | "url": "https://github.com/sponsors/sindresorhus" 304 | } 305 | }, 306 | "node_modules/diff": { 307 | "version": "5.0.0", 308 | "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", 309 | "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", 310 | "dev": true, 311 | "engines": { 312 | "node": ">=0.3.1" 313 | } 314 | }, 315 | "node_modules/emoji-regex": { 316 | "version": "8.0.0", 317 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 318 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 319 | "dev": true 320 | }, 321 | "node_modules/entities": { 322 | "version": "2.1.0", 323 | "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", 324 | "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", 325 | "dev": true, 326 | "funding": { 327 | "url": "https://github.com/fb55/entities?sponsor=1" 328 | } 329 | }, 330 | "node_modules/escalade": { 331 | "version": "3.1.1", 332 | "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", 333 | "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", 334 | "dev": true, 335 | "engines": { 336 | "node": ">=6" 337 | } 338 | }, 339 | "node_modules/escape-string-regexp": { 340 | "version": "2.0.0", 341 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", 342 | "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", 343 | "dev": true, 344 | "engines": { 345 | "node": ">=8" 346 | } 347 | }, 348 | "node_modules/fill-range": { 349 | "version": "7.0.1", 350 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", 351 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", 352 | "dev": true, 353 | "dependencies": { 354 | "to-regex-range": "^5.0.1" 355 | }, 356 | "engines": { 357 | "node": ">=8" 358 | } 359 | }, 360 | "node_modules/find-up": { 361 | "version": "5.0.0", 362 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", 363 | "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", 364 | "dev": true, 365 | "dependencies": { 366 | "locate-path": "^6.0.0", 367 | "path-exists": "^4.0.0" 368 | }, 369 | "engines": { 370 | "node": ">=10" 371 | }, 372 | "funding": { 373 | "url": "https://github.com/sponsors/sindresorhus" 374 | } 375 | }, 376 | "node_modules/flat": { 377 | "version": "5.0.2", 378 | "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", 379 | "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", 380 | "dev": true, 381 | "bin": { 382 | "flat": "cli.js" 383 | } 384 | }, 385 | "node_modules/fs.realpath": { 386 | "version": "1.0.0", 387 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 388 | "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", 389 | "dev": true 390 | }, 391 | "node_modules/fsevents": { 392 | "version": "2.3.2", 393 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", 394 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 395 | "dev": true, 396 | "hasInstallScript": true, 397 | "optional": true, 398 | "os": [ 399 | "darwin" 400 | ], 401 | "engines": { 402 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 403 | } 404 | }, 405 | "node_modules/get-caller-file": { 406 | "version": "2.0.5", 407 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", 408 | "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", 409 | "dev": true, 410 | "engines": { 411 | "node": "6.* || 8.* || >= 10.*" 412 | } 413 | }, 414 | "node_modules/glob": { 415 | "version": "7.2.0", 416 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", 417 | "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", 418 | "dev": true, 419 | "dependencies": { 420 | "fs.realpath": "^1.0.0", 421 | "inflight": "^1.0.4", 422 | "inherits": "2", 423 | "minimatch": "^3.0.4", 424 | "once": "^1.3.0", 425 | "path-is-absolute": "^1.0.0" 426 | }, 427 | "engines": { 428 | "node": "*" 429 | }, 430 | "funding": { 431 | "url": "https://github.com/sponsors/isaacs" 432 | } 433 | }, 434 | "node_modules/glob-parent": { 435 | "version": "5.1.2", 436 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 437 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 438 | "dev": true, 439 | "dependencies": { 440 | "is-glob": "^4.0.1" 441 | }, 442 | "engines": { 443 | "node": ">= 6" 444 | } 445 | }, 446 | "node_modules/glob/node_modules/minimatch": { 447 | "version": "3.1.2", 448 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 449 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 450 | "dev": true, 451 | "dependencies": { 452 | "brace-expansion": "^1.1.7" 453 | }, 454 | "engines": { 455 | "node": "*" 456 | } 457 | }, 458 | "node_modules/graceful-fs": { 459 | "version": "4.2.10", 460 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", 461 | "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", 462 | "dev": true 463 | }, 464 | "node_modules/growl": { 465 | "version": "1.10.5", 466 | "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", 467 | "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", 468 | "dev": true, 469 | "engines": { 470 | "node": ">=4.x" 471 | } 472 | }, 473 | "node_modules/has-flag": { 474 | "version": "4.0.0", 475 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 476 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 477 | "dev": true, 478 | "engines": { 479 | "node": ">=8" 480 | } 481 | }, 482 | "node_modules/he": { 483 | "version": "1.2.0", 484 | "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", 485 | "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", 486 | "dev": true, 487 | "bin": { 488 | "he": "bin/he" 489 | } 490 | }, 491 | "node_modules/inflight": { 492 | "version": "1.0.6", 493 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 494 | "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", 495 | "dev": true, 496 | "dependencies": { 497 | "once": "^1.3.0", 498 | "wrappy": "1" 499 | } 500 | }, 501 | "node_modules/inherits": { 502 | "version": "2.0.4", 503 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 504 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 505 | "dev": true 506 | }, 507 | "node_modules/is-binary-path": { 508 | "version": "2.1.0", 509 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", 510 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", 511 | "dev": true, 512 | "dependencies": { 513 | "binary-extensions": "^2.0.0" 514 | }, 515 | "engines": { 516 | "node": ">=8" 517 | } 518 | }, 519 | "node_modules/is-extglob": { 520 | "version": "2.1.1", 521 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 522 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 523 | "dev": true, 524 | "engines": { 525 | "node": ">=0.10.0" 526 | } 527 | }, 528 | "node_modules/is-fullwidth-code-point": { 529 | "version": "3.0.0", 530 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 531 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 532 | "dev": true, 533 | "engines": { 534 | "node": ">=8" 535 | } 536 | }, 537 | "node_modules/is-glob": { 538 | "version": "4.0.3", 539 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 540 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 541 | "dev": true, 542 | "dependencies": { 543 | "is-extglob": "^2.1.1" 544 | }, 545 | "engines": { 546 | "node": ">=0.10.0" 547 | } 548 | }, 549 | "node_modules/is-number": { 550 | "version": "7.0.0", 551 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 552 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 553 | "dev": true, 554 | "engines": { 555 | "node": ">=0.12.0" 556 | } 557 | }, 558 | "node_modules/is-plain-obj": { 559 | "version": "2.1.0", 560 | "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", 561 | "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", 562 | "dev": true, 563 | "engines": { 564 | "node": ">=8" 565 | } 566 | }, 567 | "node_modules/is-unicode-supported": { 568 | "version": "0.1.0", 569 | "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", 570 | "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", 571 | "dev": true, 572 | "engines": { 573 | "node": ">=10" 574 | }, 575 | "funding": { 576 | "url": "https://github.com/sponsors/sindresorhus" 577 | } 578 | }, 579 | "node_modules/isexe": { 580 | "version": "2.0.0", 581 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 582 | "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", 583 | "dev": true 584 | }, 585 | "node_modules/js-yaml": { 586 | "version": "4.1.0", 587 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", 588 | "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", 589 | "dev": true, 590 | "dependencies": { 591 | "argparse": "^2.0.1" 592 | }, 593 | "bin": { 594 | "js-yaml": "bin/js-yaml.js" 595 | } 596 | }, 597 | "node_modules/js2xmlparser": { 598 | "version": "4.0.2", 599 | "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", 600 | "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", 601 | "dev": true, 602 | "dependencies": { 603 | "xmlcreate": "^2.0.4" 604 | } 605 | }, 606 | "node_modules/jsdoc": { 607 | "version": "3.6.11", 608 | "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.11.tgz", 609 | "integrity": "sha512-8UCU0TYeIYD9KeLzEcAu2q8N/mx9O3phAGl32nmHlE0LpaJL71mMkP4d+QE5zWfNt50qheHtOZ0qoxVrsX5TUg==", 610 | "dev": true, 611 | "dependencies": { 612 | "@babel/parser": "^7.9.4", 613 | "@types/markdown-it": "^12.2.3", 614 | "bluebird": "^3.7.2", 615 | "catharsis": "^0.9.0", 616 | "escape-string-regexp": "^2.0.0", 617 | "js2xmlparser": "^4.0.2", 618 | "klaw": "^3.0.0", 619 | "markdown-it": "^12.3.2", 620 | "markdown-it-anchor": "^8.4.1", 621 | "marked": "^4.0.10", 622 | "mkdirp": "^1.0.4", 623 | "requizzle": "^0.2.3", 624 | "strip-json-comments": "^3.1.0", 625 | "taffydb": "2.6.2", 626 | "underscore": "~1.13.2" 627 | }, 628 | "bin": { 629 | "jsdoc": "jsdoc.js" 630 | }, 631 | "engines": { 632 | "node": ">=12.0.0" 633 | } 634 | }, 635 | "node_modules/klaw": { 636 | "version": "3.0.0", 637 | "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", 638 | "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", 639 | "dev": true, 640 | "dependencies": { 641 | "graceful-fs": "^4.1.9" 642 | } 643 | }, 644 | "node_modules/linkify-it": { 645 | "version": "3.0.3", 646 | "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", 647 | "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", 648 | "dev": true, 649 | "dependencies": { 650 | "uc.micro": "^1.0.1" 651 | } 652 | }, 653 | "node_modules/locate-path": { 654 | "version": "6.0.0", 655 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", 656 | "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", 657 | "dev": true, 658 | "dependencies": { 659 | "p-locate": "^5.0.0" 660 | }, 661 | "engines": { 662 | "node": ">=10" 663 | }, 664 | "funding": { 665 | "url": "https://github.com/sponsors/sindresorhus" 666 | } 667 | }, 668 | "node_modules/lodash": { 669 | "version": "4.17.21", 670 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 671 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", 672 | "dev": true 673 | }, 674 | "node_modules/log-symbols": { 675 | "version": "4.1.0", 676 | "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", 677 | "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", 678 | "dev": true, 679 | "dependencies": { 680 | "chalk": "^4.1.0", 681 | "is-unicode-supported": "^0.1.0" 682 | }, 683 | "engines": { 684 | "node": ">=10" 685 | }, 686 | "funding": { 687 | "url": "https://github.com/sponsors/sindresorhus" 688 | } 689 | }, 690 | "node_modules/markdown-it": { 691 | "version": "12.3.2", 692 | "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", 693 | "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", 694 | "dev": true, 695 | "dependencies": { 696 | "argparse": "^2.0.1", 697 | "entities": "~2.1.0", 698 | "linkify-it": "^3.0.1", 699 | "mdurl": "^1.0.1", 700 | "uc.micro": "^1.0.5" 701 | }, 702 | "bin": { 703 | "markdown-it": "bin/markdown-it.js" 704 | } 705 | }, 706 | "node_modules/markdown-it-anchor": { 707 | "version": "8.6.4", 708 | "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.4.tgz", 709 | "integrity": "sha512-Ul4YVYZNxMJYALpKtu+ZRdrryYt/GlQ5CK+4l1bp/gWXOG2QWElt6AqF3Mih/wfUKdZbNAZVXGR73/n6U/8img==", 710 | "dev": true, 711 | "peerDependencies": { 712 | "@types/markdown-it": "*", 713 | "markdown-it": "*" 714 | } 715 | }, 716 | "node_modules/marked": { 717 | "version": "4.0.18", 718 | "resolved": "https://registry.npmjs.org/marked/-/marked-4.0.18.tgz", 719 | "integrity": "sha512-wbLDJ7Zh0sqA0Vdg6aqlbT+yPxqLblpAZh1mK2+AO2twQkPywvvqQNfEPVwSSRjZ7dZcdeVBIAgiO7MMp3Dszw==", 720 | "dev": true, 721 | "bin": { 722 | "marked": "bin/marked.js" 723 | }, 724 | "engines": { 725 | "node": ">= 12" 726 | } 727 | }, 728 | "node_modules/mdurl": { 729 | "version": "1.0.1", 730 | "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", 731 | "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", 732 | "dev": true 733 | }, 734 | "node_modules/minimatch": { 735 | "version": "4.2.1", 736 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-4.2.1.tgz", 737 | "integrity": "sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==", 738 | "dev": true, 739 | "dependencies": { 740 | "brace-expansion": "^1.1.7" 741 | }, 742 | "engines": { 743 | "node": ">=10" 744 | } 745 | }, 746 | "node_modules/mkdirp": { 747 | "version": "1.0.4", 748 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", 749 | "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", 750 | "dev": true, 751 | "bin": { 752 | "mkdirp": "bin/cmd.js" 753 | }, 754 | "engines": { 755 | "node": ">=10" 756 | } 757 | }, 758 | "node_modules/mocha": { 759 | "version": "9.2.2", 760 | "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.2.tgz", 761 | "integrity": "sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==", 762 | "dev": true, 763 | "dependencies": { 764 | "@ungap/promise-all-settled": "1.1.2", 765 | "ansi-colors": "4.1.1", 766 | "browser-stdout": "1.3.1", 767 | "chokidar": "3.5.3", 768 | "debug": "4.3.3", 769 | "diff": "5.0.0", 770 | "escape-string-regexp": "4.0.0", 771 | "find-up": "5.0.0", 772 | "glob": "7.2.0", 773 | "growl": "1.10.5", 774 | "he": "1.2.0", 775 | "js-yaml": "4.1.0", 776 | "log-symbols": "4.1.0", 777 | "minimatch": "4.2.1", 778 | "ms": "2.1.3", 779 | "nanoid": "3.3.1", 780 | "serialize-javascript": "6.0.0", 781 | "strip-json-comments": "3.1.1", 782 | "supports-color": "8.1.1", 783 | "which": "2.0.2", 784 | "workerpool": "6.2.0", 785 | "yargs": "16.2.0", 786 | "yargs-parser": "20.2.4", 787 | "yargs-unparser": "2.0.0" 788 | }, 789 | "bin": { 790 | "_mocha": "bin/_mocha", 791 | "mocha": "bin/mocha" 792 | }, 793 | "engines": { 794 | "node": ">= 12.0.0" 795 | }, 796 | "funding": { 797 | "type": "opencollective", 798 | "url": "https://opencollective.com/mochajs" 799 | } 800 | }, 801 | "node_modules/mocha/node_modules/escape-string-regexp": { 802 | "version": "4.0.0", 803 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", 804 | "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", 805 | "dev": true, 806 | "engines": { 807 | "node": ">=10" 808 | }, 809 | "funding": { 810 | "url": "https://github.com/sponsors/sindresorhus" 811 | } 812 | }, 813 | "node_modules/ms": { 814 | "version": "2.1.3", 815 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 816 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 817 | "dev": true 818 | }, 819 | "node_modules/nanoid": { 820 | "version": "3.3.1", 821 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", 822 | "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", 823 | "dev": true, 824 | "bin": { 825 | "nanoid": "bin/nanoid.cjs" 826 | }, 827 | "engines": { 828 | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 829 | } 830 | }, 831 | "node_modules/normalize-path": { 832 | "version": "3.0.0", 833 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", 834 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", 835 | "dev": true, 836 | "engines": { 837 | "node": ">=0.10.0" 838 | } 839 | }, 840 | "node_modules/once": { 841 | "version": "1.4.0", 842 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 843 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 844 | "dev": true, 845 | "dependencies": { 846 | "wrappy": "1" 847 | } 848 | }, 849 | "node_modules/p-limit": { 850 | "version": "3.1.0", 851 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", 852 | "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", 853 | "dev": true, 854 | "dependencies": { 855 | "yocto-queue": "^0.1.0" 856 | }, 857 | "engines": { 858 | "node": ">=10" 859 | }, 860 | "funding": { 861 | "url": "https://github.com/sponsors/sindresorhus" 862 | } 863 | }, 864 | "node_modules/p-locate": { 865 | "version": "5.0.0", 866 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", 867 | "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", 868 | "dev": true, 869 | "dependencies": { 870 | "p-limit": "^3.0.2" 871 | }, 872 | "engines": { 873 | "node": ">=10" 874 | }, 875 | "funding": { 876 | "url": "https://github.com/sponsors/sindresorhus" 877 | } 878 | }, 879 | "node_modules/path-exists": { 880 | "version": "4.0.0", 881 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", 882 | "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", 883 | "dev": true, 884 | "engines": { 885 | "node": ">=8" 886 | } 887 | }, 888 | "node_modules/path-is-absolute": { 889 | "version": "1.0.1", 890 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 891 | "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", 892 | "dev": true, 893 | "engines": { 894 | "node": ">=0.10.0" 895 | } 896 | }, 897 | "node_modules/picomatch": { 898 | "version": "2.3.1", 899 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 900 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 901 | "dev": true, 902 | "engines": { 903 | "node": ">=8.6" 904 | }, 905 | "funding": { 906 | "url": "https://github.com/sponsors/jonschlinkert" 907 | } 908 | }, 909 | "node_modules/randombytes": { 910 | "version": "2.1.0", 911 | "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", 912 | "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", 913 | "dev": true, 914 | "dependencies": { 915 | "safe-buffer": "^5.1.0" 916 | } 917 | }, 918 | "node_modules/readdirp": { 919 | "version": "3.6.0", 920 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", 921 | "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", 922 | "dev": true, 923 | "dependencies": { 924 | "picomatch": "^2.2.1" 925 | }, 926 | "engines": { 927 | "node": ">=8.10.0" 928 | } 929 | }, 930 | "node_modules/require-directory": { 931 | "version": "2.1.1", 932 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", 933 | "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", 934 | "dev": true, 935 | "engines": { 936 | "node": ">=0.10.0" 937 | } 938 | }, 939 | "node_modules/requizzle": { 940 | "version": "0.2.3", 941 | "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.3.tgz", 942 | "integrity": "sha512-YanoyJjykPxGHii0fZP0uUPEXpvqfBDxWV7s6GKAiiOsiqhX6vHNyW3Qzdmqp/iq/ExbhaGbVrjB4ruEVSM4GQ==", 943 | "dev": true, 944 | "dependencies": { 945 | "lodash": "^4.17.14" 946 | } 947 | }, 948 | "node_modules/safe-buffer": { 949 | "version": "5.2.1", 950 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 951 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 952 | "dev": true, 953 | "funding": [ 954 | { 955 | "type": "github", 956 | "url": "https://github.com/sponsors/feross" 957 | }, 958 | { 959 | "type": "patreon", 960 | "url": "https://www.patreon.com/feross" 961 | }, 962 | { 963 | "type": "consulting", 964 | "url": "https://feross.org/support" 965 | } 966 | ] 967 | }, 968 | "node_modules/serialize-javascript": { 969 | "version": "6.0.0", 970 | "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", 971 | "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", 972 | "dev": true, 973 | "dependencies": { 974 | "randombytes": "^2.1.0" 975 | } 976 | }, 977 | "node_modules/string-width": { 978 | "version": "4.2.3", 979 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 980 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 981 | "dev": true, 982 | "dependencies": { 983 | "emoji-regex": "^8.0.0", 984 | "is-fullwidth-code-point": "^3.0.0", 985 | "strip-ansi": "^6.0.1" 986 | }, 987 | "engines": { 988 | "node": ">=8" 989 | } 990 | }, 991 | "node_modules/strip-ansi": { 992 | "version": "6.0.1", 993 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 994 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 995 | "dev": true, 996 | "dependencies": { 997 | "ansi-regex": "^5.0.1" 998 | }, 999 | "engines": { 1000 | "node": ">=8" 1001 | } 1002 | }, 1003 | "node_modules/strip-json-comments": { 1004 | "version": "3.1.1", 1005 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", 1006 | "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", 1007 | "dev": true, 1008 | "engines": { 1009 | "node": ">=8" 1010 | }, 1011 | "funding": { 1012 | "url": "https://github.com/sponsors/sindresorhus" 1013 | } 1014 | }, 1015 | "node_modules/supports-color": { 1016 | "version": "8.1.1", 1017 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", 1018 | "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", 1019 | "dev": true, 1020 | "dependencies": { 1021 | "has-flag": "^4.0.0" 1022 | }, 1023 | "engines": { 1024 | "node": ">=10" 1025 | }, 1026 | "funding": { 1027 | "url": "https://github.com/chalk/supports-color?sponsor=1" 1028 | } 1029 | }, 1030 | "node_modules/taffydb": { 1031 | "version": "2.6.2", 1032 | "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", 1033 | "integrity": "sha512-y3JaeRSplks6NYQuCOj3ZFMO3j60rTwbuKCvZxsAraGYH2epusatvZ0baZYA01WsGqJBq/Dl6vOrMUJqyMj8kA==", 1034 | "dev": true 1035 | }, 1036 | "node_modules/to-regex-range": { 1037 | "version": "5.0.1", 1038 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 1039 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 1040 | "dev": true, 1041 | "dependencies": { 1042 | "is-number": "^7.0.0" 1043 | }, 1044 | "engines": { 1045 | "node": ">=8.0" 1046 | } 1047 | }, 1048 | "node_modules/uc.micro": { 1049 | "version": "1.0.6", 1050 | "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", 1051 | "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", 1052 | "dev": true 1053 | }, 1054 | "node_modules/underscore": { 1055 | "version": "1.13.4", 1056 | "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.4.tgz", 1057 | "integrity": "sha512-BQFnUDuAQ4Yf/cYY5LNrK9NCJFKriaRbD9uR1fTeXnBeoa97W0i41qkZfGO9pSo8I5KzjAcSY2XYtdf0oKd7KQ==", 1058 | "dev": true 1059 | }, 1060 | "node_modules/which": { 1061 | "version": "2.0.2", 1062 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 1063 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 1064 | "dev": true, 1065 | "dependencies": { 1066 | "isexe": "^2.0.0" 1067 | }, 1068 | "bin": { 1069 | "node-which": "bin/node-which" 1070 | }, 1071 | "engines": { 1072 | "node": ">= 8" 1073 | } 1074 | }, 1075 | "node_modules/workerpool": { 1076 | "version": "6.2.0", 1077 | "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", 1078 | "integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==", 1079 | "dev": true 1080 | }, 1081 | "node_modules/wrap-ansi": { 1082 | "version": "7.0.0", 1083 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", 1084 | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", 1085 | "dev": true, 1086 | "dependencies": { 1087 | "ansi-styles": "^4.0.0", 1088 | "string-width": "^4.1.0", 1089 | "strip-ansi": "^6.0.0" 1090 | }, 1091 | "engines": { 1092 | "node": ">=10" 1093 | }, 1094 | "funding": { 1095 | "url": "https://github.com/chalk/wrap-ansi?sponsor=1" 1096 | } 1097 | }, 1098 | "node_modules/wrappy": { 1099 | "version": "1.0.2", 1100 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1101 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", 1102 | "dev": true 1103 | }, 1104 | "node_modules/xmlcreate": { 1105 | "version": "2.0.4", 1106 | "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", 1107 | "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==", 1108 | "dev": true 1109 | }, 1110 | "node_modules/y18n": { 1111 | "version": "5.0.8", 1112 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", 1113 | "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", 1114 | "dev": true, 1115 | "engines": { 1116 | "node": ">=10" 1117 | } 1118 | }, 1119 | "node_modules/yargs": { 1120 | "version": "16.2.0", 1121 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", 1122 | "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", 1123 | "dev": true, 1124 | "dependencies": { 1125 | "cliui": "^7.0.2", 1126 | "escalade": "^3.1.1", 1127 | "get-caller-file": "^2.0.5", 1128 | "require-directory": "^2.1.1", 1129 | "string-width": "^4.2.0", 1130 | "y18n": "^5.0.5", 1131 | "yargs-parser": "^20.2.2" 1132 | }, 1133 | "engines": { 1134 | "node": ">=10" 1135 | } 1136 | }, 1137 | "node_modules/yargs-parser": { 1138 | "version": "20.2.4", 1139 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", 1140 | "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", 1141 | "dev": true, 1142 | "engines": { 1143 | "node": ">=10" 1144 | } 1145 | }, 1146 | "node_modules/yargs-unparser": { 1147 | "version": "2.0.0", 1148 | "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", 1149 | "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", 1150 | "dev": true, 1151 | "dependencies": { 1152 | "camelcase": "^6.0.0", 1153 | "decamelize": "^4.0.0", 1154 | "flat": "^5.0.2", 1155 | "is-plain-obj": "^2.1.0" 1156 | }, 1157 | "engines": { 1158 | "node": ">=10" 1159 | } 1160 | }, 1161 | "node_modules/yocto-queue": { 1162 | "version": "0.1.0", 1163 | "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", 1164 | "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", 1165 | "dev": true, 1166 | "engines": { 1167 | "node": ">=10" 1168 | }, 1169 | "funding": { 1170 | "url": "https://github.com/sponsors/sindresorhus" 1171 | } 1172 | } 1173 | }, 1174 | "dependencies": { 1175 | "@babel/parser": { 1176 | "version": "7.18.9", 1177 | "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.9.tgz", 1178 | "integrity": "sha512-9uJveS9eY9DJ0t64YbIBZICtJy8a5QrDEVdiLCG97fVLpDTpGX7t8mMSb6OWw6Lrnjqj4O8zwjELX3dhoMgiBg==", 1179 | "dev": true 1180 | }, 1181 | "@types/linkify-it": { 1182 | "version": "3.0.2", 1183 | "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.2.tgz", 1184 | "integrity": "sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==", 1185 | "dev": true 1186 | }, 1187 | "@types/markdown-it": { 1188 | "version": "12.2.3", 1189 | "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", 1190 | "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", 1191 | "dev": true, 1192 | "requires": { 1193 | "@types/linkify-it": "*", 1194 | "@types/mdurl": "*" 1195 | } 1196 | }, 1197 | "@types/mdurl": { 1198 | "version": "1.0.2", 1199 | "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz", 1200 | "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==", 1201 | "dev": true 1202 | }, 1203 | "@ungap/promise-all-settled": { 1204 | "version": "1.1.2", 1205 | "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", 1206 | "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", 1207 | "dev": true 1208 | }, 1209 | "ansi-colors": { 1210 | "version": "4.1.1", 1211 | "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", 1212 | "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", 1213 | "dev": true 1214 | }, 1215 | "ansi-regex": { 1216 | "version": "5.0.1", 1217 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 1218 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 1219 | "dev": true 1220 | }, 1221 | "ansi-styles": { 1222 | "version": "4.3.0", 1223 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 1224 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 1225 | "dev": true, 1226 | "requires": { 1227 | "color-convert": "^2.0.1" 1228 | } 1229 | }, 1230 | "anymatch": { 1231 | "version": "3.1.2", 1232 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", 1233 | "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", 1234 | "dev": true, 1235 | "requires": { 1236 | "normalize-path": "^3.0.0", 1237 | "picomatch": "^2.0.4" 1238 | } 1239 | }, 1240 | "argparse": { 1241 | "version": "2.0.1", 1242 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", 1243 | "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", 1244 | "dev": true 1245 | }, 1246 | "balanced-match": { 1247 | "version": "1.0.2", 1248 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 1249 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 1250 | "dev": true 1251 | }, 1252 | "binary-extensions": { 1253 | "version": "2.2.0", 1254 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", 1255 | "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", 1256 | "dev": true 1257 | }, 1258 | "bluebird": { 1259 | "version": "3.7.2", 1260 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", 1261 | "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", 1262 | "dev": true 1263 | }, 1264 | "brace-expansion": { 1265 | "version": "1.1.11", 1266 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 1267 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 1268 | "dev": true, 1269 | "requires": { 1270 | "balanced-match": "^1.0.0", 1271 | "concat-map": "0.0.1" 1272 | } 1273 | }, 1274 | "braces": { 1275 | "version": "3.0.2", 1276 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", 1277 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", 1278 | "dev": true, 1279 | "requires": { 1280 | "fill-range": "^7.0.1" 1281 | } 1282 | }, 1283 | "browser-stdout": { 1284 | "version": "1.3.1", 1285 | "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", 1286 | "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", 1287 | "dev": true 1288 | }, 1289 | "camelcase": { 1290 | "version": "6.3.0", 1291 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", 1292 | "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", 1293 | "dev": true 1294 | }, 1295 | "catharsis": { 1296 | "version": "0.9.0", 1297 | "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", 1298 | "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", 1299 | "dev": true, 1300 | "requires": { 1301 | "lodash": "^4.17.15" 1302 | } 1303 | }, 1304 | "chalk": { 1305 | "version": "4.1.2", 1306 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 1307 | "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 1308 | "dev": true, 1309 | "requires": { 1310 | "ansi-styles": "^4.1.0", 1311 | "supports-color": "^7.1.0" 1312 | }, 1313 | "dependencies": { 1314 | "supports-color": { 1315 | "version": "7.2.0", 1316 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 1317 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 1318 | "dev": true, 1319 | "requires": { 1320 | "has-flag": "^4.0.0" 1321 | } 1322 | } 1323 | } 1324 | }, 1325 | "chokidar": { 1326 | "version": "3.5.3", 1327 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", 1328 | "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", 1329 | "dev": true, 1330 | "requires": { 1331 | "anymatch": "~3.1.2", 1332 | "braces": "~3.0.2", 1333 | "fsevents": "~2.3.2", 1334 | "glob-parent": "~5.1.2", 1335 | "is-binary-path": "~2.1.0", 1336 | "is-glob": "~4.0.1", 1337 | "normalize-path": "~3.0.0", 1338 | "readdirp": "~3.6.0" 1339 | } 1340 | }, 1341 | "cliui": { 1342 | "version": "7.0.4", 1343 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", 1344 | "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", 1345 | "dev": true, 1346 | "requires": { 1347 | "string-width": "^4.2.0", 1348 | "strip-ansi": "^6.0.0", 1349 | "wrap-ansi": "^7.0.0" 1350 | } 1351 | }, 1352 | "color-convert": { 1353 | "version": "2.0.1", 1354 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 1355 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 1356 | "dev": true, 1357 | "requires": { 1358 | "color-name": "~1.1.4" 1359 | } 1360 | }, 1361 | "color-name": { 1362 | "version": "1.1.4", 1363 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 1364 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 1365 | "dev": true 1366 | }, 1367 | "concat-map": { 1368 | "version": "0.0.1", 1369 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 1370 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", 1371 | "dev": true 1372 | }, 1373 | "debug": { 1374 | "version": "4.3.3", 1375 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", 1376 | "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", 1377 | "dev": true, 1378 | "requires": { 1379 | "ms": "2.1.2" 1380 | }, 1381 | "dependencies": { 1382 | "ms": { 1383 | "version": "2.1.2", 1384 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 1385 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 1386 | "dev": true 1387 | } 1388 | } 1389 | }, 1390 | "decamelize": { 1391 | "version": "4.0.0", 1392 | "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", 1393 | "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", 1394 | "dev": true 1395 | }, 1396 | "diff": { 1397 | "version": "5.0.0", 1398 | "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", 1399 | "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", 1400 | "dev": true 1401 | }, 1402 | "emoji-regex": { 1403 | "version": "8.0.0", 1404 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 1405 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 1406 | "dev": true 1407 | }, 1408 | "entities": { 1409 | "version": "2.1.0", 1410 | "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", 1411 | "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", 1412 | "dev": true 1413 | }, 1414 | "escalade": { 1415 | "version": "3.1.1", 1416 | "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", 1417 | "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", 1418 | "dev": true 1419 | }, 1420 | "escape-string-regexp": { 1421 | "version": "2.0.0", 1422 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", 1423 | "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", 1424 | "dev": true 1425 | }, 1426 | "fill-range": { 1427 | "version": "7.0.1", 1428 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", 1429 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", 1430 | "dev": true, 1431 | "requires": { 1432 | "to-regex-range": "^5.0.1" 1433 | } 1434 | }, 1435 | "find-up": { 1436 | "version": "5.0.0", 1437 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", 1438 | "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", 1439 | "dev": true, 1440 | "requires": { 1441 | "locate-path": "^6.0.0", 1442 | "path-exists": "^4.0.0" 1443 | } 1444 | }, 1445 | "flat": { 1446 | "version": "5.0.2", 1447 | "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", 1448 | "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", 1449 | "dev": true 1450 | }, 1451 | "fs.realpath": { 1452 | "version": "1.0.0", 1453 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 1454 | "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", 1455 | "dev": true 1456 | }, 1457 | "fsevents": { 1458 | "version": "2.3.2", 1459 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", 1460 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 1461 | "dev": true, 1462 | "optional": true 1463 | }, 1464 | "get-caller-file": { 1465 | "version": "2.0.5", 1466 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", 1467 | "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", 1468 | "dev": true 1469 | }, 1470 | "glob": { 1471 | "version": "7.2.0", 1472 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", 1473 | "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", 1474 | "dev": true, 1475 | "requires": { 1476 | "fs.realpath": "^1.0.0", 1477 | "inflight": "^1.0.4", 1478 | "inherits": "2", 1479 | "minimatch": "^3.0.4", 1480 | "once": "^1.3.0", 1481 | "path-is-absolute": "^1.0.0" 1482 | }, 1483 | "dependencies": { 1484 | "minimatch": { 1485 | "version": "3.1.2", 1486 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 1487 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 1488 | "dev": true, 1489 | "requires": { 1490 | "brace-expansion": "^1.1.7" 1491 | } 1492 | } 1493 | } 1494 | }, 1495 | "glob-parent": { 1496 | "version": "5.1.2", 1497 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 1498 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 1499 | "dev": true, 1500 | "requires": { 1501 | "is-glob": "^4.0.1" 1502 | } 1503 | }, 1504 | "graceful-fs": { 1505 | "version": "4.2.10", 1506 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", 1507 | "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", 1508 | "dev": true 1509 | }, 1510 | "growl": { 1511 | "version": "1.10.5", 1512 | "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", 1513 | "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", 1514 | "dev": true 1515 | }, 1516 | "has-flag": { 1517 | "version": "4.0.0", 1518 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 1519 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 1520 | "dev": true 1521 | }, 1522 | "he": { 1523 | "version": "1.2.0", 1524 | "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", 1525 | "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", 1526 | "dev": true 1527 | }, 1528 | "inflight": { 1529 | "version": "1.0.6", 1530 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 1531 | "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", 1532 | "dev": true, 1533 | "requires": { 1534 | "once": "^1.3.0", 1535 | "wrappy": "1" 1536 | } 1537 | }, 1538 | "inherits": { 1539 | "version": "2.0.4", 1540 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 1541 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 1542 | "dev": true 1543 | }, 1544 | "is-binary-path": { 1545 | "version": "2.1.0", 1546 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", 1547 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", 1548 | "dev": true, 1549 | "requires": { 1550 | "binary-extensions": "^2.0.0" 1551 | } 1552 | }, 1553 | "is-extglob": { 1554 | "version": "2.1.1", 1555 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 1556 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 1557 | "dev": true 1558 | }, 1559 | "is-fullwidth-code-point": { 1560 | "version": "3.0.0", 1561 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 1562 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 1563 | "dev": true 1564 | }, 1565 | "is-glob": { 1566 | "version": "4.0.3", 1567 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 1568 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 1569 | "dev": true, 1570 | "requires": { 1571 | "is-extglob": "^2.1.1" 1572 | } 1573 | }, 1574 | "is-number": { 1575 | "version": "7.0.0", 1576 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 1577 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 1578 | "dev": true 1579 | }, 1580 | "is-plain-obj": { 1581 | "version": "2.1.0", 1582 | "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", 1583 | "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", 1584 | "dev": true 1585 | }, 1586 | "is-unicode-supported": { 1587 | "version": "0.1.0", 1588 | "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", 1589 | "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", 1590 | "dev": true 1591 | }, 1592 | "isexe": { 1593 | "version": "2.0.0", 1594 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 1595 | "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", 1596 | "dev": true 1597 | }, 1598 | "js-yaml": { 1599 | "version": "4.1.0", 1600 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", 1601 | "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", 1602 | "dev": true, 1603 | "requires": { 1604 | "argparse": "^2.0.1" 1605 | } 1606 | }, 1607 | "js2xmlparser": { 1608 | "version": "4.0.2", 1609 | "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", 1610 | "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", 1611 | "dev": true, 1612 | "requires": { 1613 | "xmlcreate": "^2.0.4" 1614 | } 1615 | }, 1616 | "jsdoc": { 1617 | "version": "3.6.11", 1618 | "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.11.tgz", 1619 | "integrity": "sha512-8UCU0TYeIYD9KeLzEcAu2q8N/mx9O3phAGl32nmHlE0LpaJL71mMkP4d+QE5zWfNt50qheHtOZ0qoxVrsX5TUg==", 1620 | "dev": true, 1621 | "requires": { 1622 | "@babel/parser": "^7.9.4", 1623 | "@types/markdown-it": "^12.2.3", 1624 | "bluebird": "^3.7.2", 1625 | "catharsis": "^0.9.0", 1626 | "escape-string-regexp": "^2.0.0", 1627 | "js2xmlparser": "^4.0.2", 1628 | "klaw": "^3.0.0", 1629 | "markdown-it": "^12.3.2", 1630 | "markdown-it-anchor": "^8.4.1", 1631 | "marked": "^4.0.10", 1632 | "mkdirp": "^1.0.4", 1633 | "requizzle": "^0.2.3", 1634 | "strip-json-comments": "^3.1.0", 1635 | "taffydb": "2.6.2", 1636 | "underscore": "~1.13.2" 1637 | } 1638 | }, 1639 | "klaw": { 1640 | "version": "3.0.0", 1641 | "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", 1642 | "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", 1643 | "dev": true, 1644 | "requires": { 1645 | "graceful-fs": "^4.1.9" 1646 | } 1647 | }, 1648 | "linkify-it": { 1649 | "version": "3.0.3", 1650 | "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", 1651 | "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", 1652 | "dev": true, 1653 | "requires": { 1654 | "uc.micro": "^1.0.1" 1655 | } 1656 | }, 1657 | "locate-path": { 1658 | "version": "6.0.0", 1659 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", 1660 | "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", 1661 | "dev": true, 1662 | "requires": { 1663 | "p-locate": "^5.0.0" 1664 | } 1665 | }, 1666 | "lodash": { 1667 | "version": "4.17.21", 1668 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 1669 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", 1670 | "dev": true 1671 | }, 1672 | "log-symbols": { 1673 | "version": "4.1.0", 1674 | "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", 1675 | "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", 1676 | "dev": true, 1677 | "requires": { 1678 | "chalk": "^4.1.0", 1679 | "is-unicode-supported": "^0.1.0" 1680 | } 1681 | }, 1682 | "markdown-it": { 1683 | "version": "12.3.2", 1684 | "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", 1685 | "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", 1686 | "dev": true, 1687 | "requires": { 1688 | "argparse": "^2.0.1", 1689 | "entities": "~2.1.0", 1690 | "linkify-it": "^3.0.1", 1691 | "mdurl": "^1.0.1", 1692 | "uc.micro": "^1.0.5" 1693 | } 1694 | }, 1695 | "markdown-it-anchor": { 1696 | "version": "8.6.4", 1697 | "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.4.tgz", 1698 | "integrity": "sha512-Ul4YVYZNxMJYALpKtu+ZRdrryYt/GlQ5CK+4l1bp/gWXOG2QWElt6AqF3Mih/wfUKdZbNAZVXGR73/n6U/8img==", 1699 | "dev": true, 1700 | "requires": {} 1701 | }, 1702 | "marked": { 1703 | "version": "4.0.18", 1704 | "resolved": "https://registry.npmjs.org/marked/-/marked-4.0.18.tgz", 1705 | "integrity": "sha512-wbLDJ7Zh0sqA0Vdg6aqlbT+yPxqLblpAZh1mK2+AO2twQkPywvvqQNfEPVwSSRjZ7dZcdeVBIAgiO7MMp3Dszw==", 1706 | "dev": true 1707 | }, 1708 | "mdurl": { 1709 | "version": "1.0.1", 1710 | "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", 1711 | "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", 1712 | "dev": true 1713 | }, 1714 | "minimatch": { 1715 | "version": "4.2.1", 1716 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-4.2.1.tgz", 1717 | "integrity": "sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==", 1718 | "dev": true, 1719 | "requires": { 1720 | "brace-expansion": "^1.1.7" 1721 | } 1722 | }, 1723 | "mkdirp": { 1724 | "version": "1.0.4", 1725 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", 1726 | "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", 1727 | "dev": true 1728 | }, 1729 | "mocha": { 1730 | "version": "9.2.2", 1731 | "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.2.tgz", 1732 | "integrity": "sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==", 1733 | "dev": true, 1734 | "requires": { 1735 | "@ungap/promise-all-settled": "1.1.2", 1736 | "ansi-colors": "4.1.1", 1737 | "browser-stdout": "1.3.1", 1738 | "chokidar": "3.5.3", 1739 | "debug": "4.3.3", 1740 | "diff": "5.0.0", 1741 | "escape-string-regexp": "4.0.0", 1742 | "find-up": "5.0.0", 1743 | "glob": "7.2.0", 1744 | "growl": "1.10.5", 1745 | "he": "1.2.0", 1746 | "js-yaml": "4.1.0", 1747 | "log-symbols": "4.1.0", 1748 | "minimatch": "4.2.1", 1749 | "ms": "2.1.3", 1750 | "nanoid": "3.3.1", 1751 | "serialize-javascript": "6.0.0", 1752 | "strip-json-comments": "3.1.1", 1753 | "supports-color": "8.1.1", 1754 | "which": "2.0.2", 1755 | "workerpool": "6.2.0", 1756 | "yargs": "16.2.0", 1757 | "yargs-parser": "20.2.4", 1758 | "yargs-unparser": "2.0.0" 1759 | }, 1760 | "dependencies": { 1761 | "escape-string-regexp": { 1762 | "version": "4.0.0", 1763 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", 1764 | "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", 1765 | "dev": true 1766 | } 1767 | } 1768 | }, 1769 | "ms": { 1770 | "version": "2.1.3", 1771 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 1772 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 1773 | "dev": true 1774 | }, 1775 | "nanoid": { 1776 | "version": "3.3.1", 1777 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", 1778 | "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", 1779 | "dev": true 1780 | }, 1781 | "normalize-path": { 1782 | "version": "3.0.0", 1783 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", 1784 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", 1785 | "dev": true 1786 | }, 1787 | "once": { 1788 | "version": "1.4.0", 1789 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1790 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 1791 | "dev": true, 1792 | "requires": { 1793 | "wrappy": "1" 1794 | } 1795 | }, 1796 | "p-limit": { 1797 | "version": "3.1.0", 1798 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", 1799 | "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", 1800 | "dev": true, 1801 | "requires": { 1802 | "yocto-queue": "^0.1.0" 1803 | } 1804 | }, 1805 | "p-locate": { 1806 | "version": "5.0.0", 1807 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", 1808 | "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", 1809 | "dev": true, 1810 | "requires": { 1811 | "p-limit": "^3.0.2" 1812 | } 1813 | }, 1814 | "path-exists": { 1815 | "version": "4.0.0", 1816 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", 1817 | "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", 1818 | "dev": true 1819 | }, 1820 | "path-is-absolute": { 1821 | "version": "1.0.1", 1822 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 1823 | "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", 1824 | "dev": true 1825 | }, 1826 | "picomatch": { 1827 | "version": "2.3.1", 1828 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 1829 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 1830 | "dev": true 1831 | }, 1832 | "randombytes": { 1833 | "version": "2.1.0", 1834 | "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", 1835 | "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", 1836 | "dev": true, 1837 | "requires": { 1838 | "safe-buffer": "^5.1.0" 1839 | } 1840 | }, 1841 | "readdirp": { 1842 | "version": "3.6.0", 1843 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", 1844 | "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", 1845 | "dev": true, 1846 | "requires": { 1847 | "picomatch": "^2.2.1" 1848 | } 1849 | }, 1850 | "require-directory": { 1851 | "version": "2.1.1", 1852 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", 1853 | "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", 1854 | "dev": true 1855 | }, 1856 | "requizzle": { 1857 | "version": "0.2.3", 1858 | "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.3.tgz", 1859 | "integrity": "sha512-YanoyJjykPxGHii0fZP0uUPEXpvqfBDxWV7s6GKAiiOsiqhX6vHNyW3Qzdmqp/iq/ExbhaGbVrjB4ruEVSM4GQ==", 1860 | "dev": true, 1861 | "requires": { 1862 | "lodash": "^4.17.14" 1863 | } 1864 | }, 1865 | "safe-buffer": { 1866 | "version": "5.2.1", 1867 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 1868 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 1869 | "dev": true 1870 | }, 1871 | "serialize-javascript": { 1872 | "version": "6.0.0", 1873 | "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", 1874 | "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", 1875 | "dev": true, 1876 | "requires": { 1877 | "randombytes": "^2.1.0" 1878 | } 1879 | }, 1880 | "string-width": { 1881 | "version": "4.2.3", 1882 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 1883 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 1884 | "dev": true, 1885 | "requires": { 1886 | "emoji-regex": "^8.0.0", 1887 | "is-fullwidth-code-point": "^3.0.0", 1888 | "strip-ansi": "^6.0.1" 1889 | } 1890 | }, 1891 | "strip-ansi": { 1892 | "version": "6.0.1", 1893 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 1894 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 1895 | "dev": true, 1896 | "requires": { 1897 | "ansi-regex": "^5.0.1" 1898 | } 1899 | }, 1900 | "strip-json-comments": { 1901 | "version": "3.1.1", 1902 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", 1903 | "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", 1904 | "dev": true 1905 | }, 1906 | "supports-color": { 1907 | "version": "8.1.1", 1908 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", 1909 | "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", 1910 | "dev": true, 1911 | "requires": { 1912 | "has-flag": "^4.0.0" 1913 | } 1914 | }, 1915 | "taffydb": { 1916 | "version": "2.6.2", 1917 | "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", 1918 | "integrity": "sha512-y3JaeRSplks6NYQuCOj3ZFMO3j60rTwbuKCvZxsAraGYH2epusatvZ0baZYA01WsGqJBq/Dl6vOrMUJqyMj8kA==", 1919 | "dev": true 1920 | }, 1921 | "to-regex-range": { 1922 | "version": "5.0.1", 1923 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 1924 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 1925 | "dev": true, 1926 | "requires": { 1927 | "is-number": "^7.0.0" 1928 | } 1929 | }, 1930 | "uc.micro": { 1931 | "version": "1.0.6", 1932 | "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", 1933 | "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", 1934 | "dev": true 1935 | }, 1936 | "underscore": { 1937 | "version": "1.13.4", 1938 | "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.4.tgz", 1939 | "integrity": "sha512-BQFnUDuAQ4Yf/cYY5LNrK9NCJFKriaRbD9uR1fTeXnBeoa97W0i41qkZfGO9pSo8I5KzjAcSY2XYtdf0oKd7KQ==", 1940 | "dev": true 1941 | }, 1942 | "which": { 1943 | "version": "2.0.2", 1944 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 1945 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 1946 | "dev": true, 1947 | "requires": { 1948 | "isexe": "^2.0.0" 1949 | } 1950 | }, 1951 | "workerpool": { 1952 | "version": "6.2.0", 1953 | "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", 1954 | "integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==", 1955 | "dev": true 1956 | }, 1957 | "wrap-ansi": { 1958 | "version": "7.0.0", 1959 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", 1960 | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", 1961 | "dev": true, 1962 | "requires": { 1963 | "ansi-styles": "^4.0.0", 1964 | "string-width": "^4.1.0", 1965 | "strip-ansi": "^6.0.0" 1966 | } 1967 | }, 1968 | "wrappy": { 1969 | "version": "1.0.2", 1970 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1971 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", 1972 | "dev": true 1973 | }, 1974 | "xmlcreate": { 1975 | "version": "2.0.4", 1976 | "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", 1977 | "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==", 1978 | "dev": true 1979 | }, 1980 | "y18n": { 1981 | "version": "5.0.8", 1982 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", 1983 | "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", 1984 | "dev": true 1985 | }, 1986 | "yargs": { 1987 | "version": "16.2.0", 1988 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", 1989 | "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", 1990 | "dev": true, 1991 | "requires": { 1992 | "cliui": "^7.0.2", 1993 | "escalade": "^3.1.1", 1994 | "get-caller-file": "^2.0.5", 1995 | "require-directory": "^2.1.1", 1996 | "string-width": "^4.2.0", 1997 | "y18n": "^5.0.5", 1998 | "yargs-parser": "^20.2.2" 1999 | } 2000 | }, 2001 | "yargs-parser": { 2002 | "version": "20.2.4", 2003 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", 2004 | "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", 2005 | "dev": true 2006 | }, 2007 | "yargs-unparser": { 2008 | "version": "2.0.0", 2009 | "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", 2010 | "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", 2011 | "dev": true, 2012 | "requires": { 2013 | "camelcase": "^6.0.0", 2014 | "decamelize": "^4.0.0", 2015 | "flat": "^5.0.2", 2016 | "is-plain-obj": "^2.1.0" 2017 | } 2018 | }, 2019 | "yocto-queue": { 2020 | "version": "0.1.0", 2021 | "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", 2022 | "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", 2023 | "dev": true 2024 | } 2025 | } 2026 | } 2027 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nanosearch", 3 | "version": "2.0.0-dev", 4 | "description": "A tiny search engine.", 5 | "main": "src/index.js", 6 | "type": "module", 7 | "directories": { 8 | "test": "tests" 9 | }, 10 | "scripts": { 11 | "test": "mocha" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/toastdriven/nanosearch.git" 16 | }, 17 | "keywords": [ 18 | "search", 19 | "n-gram", 20 | "engine" 21 | ], 22 | "author": "Daniel Lindsley ", 23 | "license": "BSD-3-Clause", 24 | "bugs": { 25 | "url": "https://github.com/toastdriven/nanosearch/issues" 26 | }, 27 | "homepage": "https://github.com/toastdriven/nanosearch#readme", 28 | "devDependencies": { 29 | "jsdoc": "^3.6.11", 30 | "mocha": "^9.1.3" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * nanosearch: A tiny search engine. 3 | * 4 | * Default implementation is based off of n-grams (default size: `3`). 5 | * 6 | * @module nanosearch 7 | */ 8 | 9 | const VERSION = "2.0.0-dev"; 10 | 11 | const ENGLISH_STOP_WORDS = [ 12 | "a", 13 | "all", 14 | "am", 15 | "an", 16 | "and", 17 | "any", 18 | "are", 19 | "at", 20 | "be", 21 | "been", 22 | "being", 23 | "both", 24 | "but", 25 | "by", 26 | "can", 27 | "can't", 28 | "did", 29 | "didn't", 30 | "does", 31 | "doesn't", 32 | "doing", 33 | "done", 34 | "down", 35 | "each", 36 | "few", 37 | "for", 38 | "had", 39 | "has", 40 | "have", 41 | "he", 42 | "her", 43 | "here", 44 | "herself", 45 | "him", 46 | "himself", 47 | "how", 48 | "i", 49 | "if", 50 | "in", 51 | "into", 52 | "is", 53 | "isn't", 54 | "it", 55 | "its", 56 | "itself", 57 | "many", 58 | "me", 59 | "more", 60 | "most", 61 | "my", 62 | "no", 63 | "nor", 64 | "not", 65 | "now", 66 | "of", 67 | "off", 68 | "on", 69 | "once", 70 | "only", 71 | "or", 72 | "other", 73 | "our", 74 | "out", 75 | "own", 76 | "same", 77 | "should", 78 | "shouldn't", 79 | "so", 80 | "some", 81 | "than", 82 | "that", 83 | "the", 84 | "these", 85 | "their", 86 | "theirs", 87 | "them", 88 | "themselves", 89 | "then", 90 | "they", 91 | "this", 92 | "those", 93 | "to", 94 | "too", 95 | "up", 96 | "very", 97 | "we", 98 | "what", 99 | "when", 100 | "where", 101 | "which", 102 | "who", 103 | "whom", 104 | "why", 105 | "you", 106 | ]; 107 | 108 | /** 109 | * A term position. 110 | * 111 | * Essentially a struct, this takes a term (word) & a position within a document. 112 | */ 113 | class TermPosition { 114 | /** 115 | * Creates a new term position. 116 | * @param {string} term - The term/word to store. 117 | * @param {int} position - The position within the document. 118 | * @return {this} 119 | */ 120 | constructor(term, position) { 121 | this.term = term; 122 | this.position = position; 123 | } 124 | } 125 | 126 | function* _resultsIterator(results, start = 0, end = Infinity, step = 1) { 127 | let iterationCount = 0; 128 | let actualEnd = Math.min(results.length, end); 129 | 130 | for (let i = start; i < actualEnd; i += step) { 131 | iterationCount++; 132 | yield results.at(i); 133 | } 134 | 135 | return iterationCount; 136 | } 137 | 138 | /** 139 | * A result set. 140 | * 141 | * An object that makes working with all the results from a query easier. 142 | */ 143 | class Results { 144 | /** 145 | * Creates a new result. 146 | * @param {string} query - The query searched on. 147 | * @param {array} results - The results found from searching. 148 | * @return {this} 149 | */ 150 | constructor(query, results) { 151 | this.query = query; 152 | this._allResults = results || []; 153 | } 154 | 155 | /** 156 | * Creates an iterator that allows you to loop over results. 157 | * @param {int} start - The starting offset. Default is `0`. 158 | * @param {int} end - The ending offset. Default is `Infinity`. 159 | * @param {int} step - The step value. Default is `1`. 160 | * @return {generator} 161 | */ 162 | iterator(start = 0, end = Infinity, step = 1) { 163 | return _resultsIterator(this._allResults, start, end, step); 164 | } 165 | 166 | /** 167 | * Returns a result at a specific offset. 168 | * @param {int} offset - The position within the results. 169 | * @return {this} 170 | */ 171 | at(offset) { 172 | return this._allResults.at(offset); 173 | } 174 | 175 | /** 176 | * Returns the total count of matches. 177 | * @return {int} 178 | */ 179 | count() { 180 | return this._allResults.length; 181 | } 182 | 183 | /** 184 | * Slices the result set. 185 | * @param {int} start - The starting offset. 186 | * @param {int} end - The ending offset (exclusive). 187 | * @return {array} 188 | */ 189 | slice(start, end) { 190 | return this._allResults.slice(start, end); 191 | } 192 | } 193 | 194 | /** 195 | * A basic preprocessor. 196 | * 197 | * Takes a document of text & generates a list of words. 198 | * 199 | * Functionally, this essentially does the following: 200 | * - converts the document to lowercase 201 | * - strips out non-alphanumeric symbols 202 | * - splits on whitespace 203 | */ 204 | class BasicPreprocessor { 205 | /** 206 | * Creates a new basic preprocessor. 207 | * @param {RegExp} splitOn - What to split terms on. Default is any 208 | * whitespace. 209 | * @param {array} stopWords - A list of common words to be skipped. Default is 210 | * `[]`. 211 | * @param {RegExp} punctuation - Any punctuation to be removed. Default is all 212 | * common symbols on an English QWERTY keyboard. 213 | * @return {this} 214 | */ 215 | constructor(splitOn, stopWords, punctuation) { 216 | this.splitOn = splitOn || /\s/; 217 | this.stopWords = stopWords || []; 218 | this.punctuation = punctuation || /[~`!@#$%^&*\(\)_+=\[\]\{\}\\\|;:'",\.\/<>?-]/g; 219 | 220 | this.setup(); 221 | } 222 | 223 | setup() { 224 | this.stopWords = this.stopWords.map((word) => this.clean(word)); 225 | } 226 | 227 | /** 228 | * Cleans a word. 229 | * 230 | * Currently, this is just stripping out basic punctuation characters. 231 | * @param {string} word - The word to be cleaned. 232 | * @return {string} 233 | */ 234 | clean(word) { 235 | // Dupe it so that we don't modify the original. 236 | let cleaned = word.slice(); 237 | cleaned = cleaned.replaceAll(this.punctuation, ""); 238 | return cleaned; 239 | } 240 | 241 | /** 242 | * Potentially appends a new term to the terms list. 243 | * 244 | * This is largely an _internal_ method. 245 | * 246 | * This lowercases, then cleans the word. If there are any characters left 247 | * post-cleaning, it will create a new `TermPosition`, and append it to 248 | * the `terms` list **IN-PLACE**. 249 | * @param {array} terms - The existing term list. 250 | * @param {string} currentWord - The word to added. 251 | * @param {int} wordOffset - The offset of the word within the document. 252 | * @return {array} 253 | */ 254 | appendTerm(terms, currentWord, wordOffset) { 255 | // We've encountered the end of the word. Finalize the term. 256 | const cleaned = this.clean(currentWord.toLowerCase()); 257 | 258 | if (cleaned.length > 0) { 259 | // Check the stop word list. 260 | if (this.stopWords.indexOf(cleaned) < 0) { 261 | // It's not a stop word. Add it to terms. 262 | const term = new TermPosition(cleaned, wordOffset); 263 | // I don't love that we're modifying the passed array in-place, but 264 | // we also don't want O(N) copies of the `terms` per-document. 265 | // That's a lot of RAM & allocations on big documents. 266 | terms.push(term); 267 | } 268 | } 269 | 270 | return terms; 271 | } 272 | 273 | /** 274 | * Processes a document into a list of terms (`TermPosition` objects). 275 | * @param {string} doc - The text to preprocess for the engine. 276 | * @return {array} 277 | */ 278 | process(doc) { 279 | const docLength = doc.length; 280 | const terms = []; 281 | let char = null, 282 | wordOffset = null, 283 | currentWord = "", 284 | cleaned = ""; 285 | 286 | // Iterate over the whole document, character by character. 287 | // `.split()` would be faster, but we would lose term positions, plus 288 | // needing to copy the document. 289 | for (let offset = 0; offset < docLength; offset++) { 290 | char = doc[offset]; 291 | 292 | // Check if it's a non-whitespace character. 293 | if (!this.splitOn.test(char)) { 294 | // Check to see if we have an active word offset. 295 | if (wordOffset === null) { 296 | // We don't have an active word. Store the offset. 297 | wordOffset = offset; 298 | } 299 | 300 | // Then append the character & move on. 301 | currentWord += char; 302 | continue; 303 | } 304 | 305 | // It's a whitespace character. 306 | // We now need to figure out if we have an in-progress term... 307 | if (currentWord.length > 0) { 308 | this.appendTerm(terms, currentWord, wordOffset); 309 | 310 | // Reset our variables. 311 | currentWord = ""; 312 | wordOffset = null; 313 | // All done, next character please! 314 | continue; 315 | } 316 | } 317 | 318 | // If we didn't hit trailing whitespace, we need to finalize the last term. 319 | if (currentWord.length > 0) { 320 | this.appendTerm(terms, currentWord, wordOffset); 321 | } 322 | 323 | return terms; 324 | } 325 | } 326 | 327 | /** 328 | * A English-specific preprocessor. 329 | * 330 | * In addition to everything the `BasicPreprocessor` does, this has a default 331 | * set of stop words suitable for the English language, which filters out 332 | * common terms. 333 | */ 334 | class EnglishPreprocessor extends BasicPreprocessor { 335 | /** 336 | * Creates a new English preprocessor. 337 | * @param {RegExp} splitOn - What to split terms on. Default is any 338 | * whitespace. 339 | * @param {array} stopWords - A list of common words to be skipped. Default is 340 | * `ENGLISH_STOP_WORDS`. 341 | * @param {RegExp} punctuation - Any punctuation to be removed. Default is all 342 | * common symbols on an English QWERTY keyboard. 343 | * @return {this} 344 | */ 345 | constructor(splitOn, stopWords, punctuation) { 346 | stopWords = stopWords || ENGLISH_STOP_WORDS; 347 | super(splitOn, stopWords, punctuation); 348 | } 349 | } 350 | 351 | /** 352 | * An n-gram tokenizer. 353 | * 354 | * Takes a word & generates a list of tokens to index. 355 | */ 356 | class NGramTokenizer { 357 | /** 358 | * Creates a new tokenizer. 359 | * @param {int} length - The minimum length of the n-grams. 360 | * @return {this} 361 | */ 362 | constructor(length) { 363 | this._gram_length = parseInt(length || 3); 364 | } 365 | 366 | /** 367 | * Processes a word into a list of tokens. 368 | * @param {string} word - The word to process. 369 | * @return {array} 370 | */ 371 | tokenize(word) { 372 | const tokens = []; 373 | 374 | if (word.length < this._gram_length) { 375 | tokens.push(word); 376 | return tokens; 377 | } 378 | 379 | // Pass a "window" over the word. 380 | for (let start = 0; start <= (word.length - this._gram_length); start++) { 381 | tokens.push(word.substr(start, this._gram_length)); 382 | } 383 | 384 | return tokens; 385 | } 386 | } 387 | 388 | /** 389 | * An regular expression tokenizer. 390 | * 391 | * Takes a word & generates a list of tokens to index. 392 | */ 393 | class RegExpTokenizer { 394 | /** 395 | * Creates a new tokenizer. 396 | * @param {RegExp} regexp - The regular expression to replace on. Default is 397 | * `/(es|ed|ing|s|able)$/g`. 398 | * @param {int} minLength - The minimum resultant word length. Default is `4`. 399 | * @return {this} 400 | */ 401 | constructor(regexp, minLength) { 402 | this.regexp = regexp || /(es|ed|ing|s|able)$/g; 403 | this.minLength = minLength || 4; 404 | } 405 | 406 | /** 407 | * Processes a word into a list of tokens. 408 | * @param {string} word - The word to process. 409 | * @return {array} 410 | */ 411 | tokenize(word) { 412 | const tokens = []; 413 | const stemmed = word.replace(this.regexp, ''); 414 | 415 | if (stemmed.length >= this.minLength) { 416 | tokens.push(stemmed); 417 | } else { 418 | // It's too short to be a useful stem. Use the original word instead. 419 | tokens.push(word); 420 | } 421 | 422 | return tokens; 423 | } 424 | } 425 | 426 | /** 427 | * A tiny search engine. 428 | * 429 | * Usage: 430 | * 431 | * const engine = new SearchEngine(); 432 | * 433 | * // Add some documents. Call for each document you need to index. 434 | * engine.add(uniqueDocumentId, documentText); 435 | * 436 | * // Later, you can let the user search & return the first 10 results. 437 | * const results = engine.search(userQuery, 10); 438 | */ 439 | class SearchEngine { 440 | /** 441 | * Creates a new search engine. 442 | * @param {object} existingIndex - The existing index or `undefined` (fresh 443 | * index). 444 | * @param {object} preprocessor - The preprocessor or `undefined` 445 | * (`BasicPreprocessor`). 446 | * @param {object} tokenizer - The tokenizer or `undefined` 447 | * (`NGramTokenizer`). 448 | * @return {this} 449 | */ 450 | constructor(existingIndex, preprocessor, tokenizer) { 451 | this.index = existingIndex || { 452 | "version": VERSION, 453 | "documentIds": {}, 454 | "terms": {}, 455 | }; 456 | this.preprocessor = preprocessor || new BasicPreprocessor(); 457 | this.tokenizer = tokenizer || new NGramTokenizer(); 458 | } 459 | 460 | /** 461 | * Clears out the entire index. 462 | * @return {undefined} 463 | */ 464 | clear() { 465 | this.index = { 466 | "version": VERSION, 467 | "documentIds": {}, 468 | "terms": {}, 469 | }; 470 | } 471 | 472 | /** 473 | * Adds a document to the index. 474 | * @param {string} docId - The unique identifier for the document. 475 | * @param {string} doc - The text of the document. 476 | * @return {undefined} 477 | */ 478 | add(docId, doc) { 479 | const alreadyIndexed = this.index["documentIds"].hasOwnProperty(docId); 480 | const terms = this.preprocessor.process(doc); 481 | 482 | for (let termPos of terms) { 483 | let tokens = this.tokenizer.tokenize(termPos.term); 484 | 485 | for (let token of tokens) { 486 | // Check if the token is already present. If not, set to an empty object. 487 | if (!this.index["terms"].hasOwnProperty(token)) { 488 | this.index["terms"][token] = {}; 489 | } 490 | 491 | // Check if the document Id is already present. If not, or if we're 492 | // reindexing it (already seen), set the count to zero. 493 | if ( 494 | (!this.index["terms"][token].hasOwnProperty(docId)) 495 | || (alreadyIndexed) 496 | ) { 497 | this.index["terms"][token][docId] = []; 498 | } 499 | 500 | // Store the term position of the token. 501 | this.index["terms"][token][docId].push(termPos.position); 502 | } 503 | } 504 | 505 | // Finally, add the document to the known list. 506 | if (!alreadyIndexed) { 507 | this.index["documentIds"][docId] = doc.length; 508 | } 509 | } 510 | 511 | /** 512 | * Removes a document from the index. 513 | * 514 | * Safe to call even if the document isn't indexed. 515 | * 516 | * WARNING: Depending on the size of your index, this may take a LONG time. 517 | * @param {string} docId - The unique identifier for the document. 518 | * @return {undefined} 519 | */ 520 | remove(docId) { 521 | const docPresent = this.index["documentIds"].hasOwnProperty(docId); 522 | 523 | if (!docPresent) { 524 | // Not found. We're all done! 525 | return; 526 | } 527 | 528 | // Remove it from the known documents list. 529 | delete this.index["documentIds"][docId]; 530 | 531 | // Then remove it from any/all tokens. 532 | for (let [term, termInfo] of Object.entries(this.index["terms"])) { 533 | if (termInfo.hasOwnProperty(docId)) { 534 | delete termInfo[docId]; 535 | } 536 | 537 | if (Object.keys(termInfo).length <= 0) { 538 | delete this.index["terms"][term]; 539 | } 540 | } 541 | } 542 | 543 | _search(query) { 544 | const initialResults = {}; 545 | const rawResults = []; 546 | const queryTerms = this.preprocessor.process(query); 547 | 548 | for (let termPos of queryTerms) { 549 | const queryTokens = this.tokenizer.tokenize(termPos.term); 550 | 551 | for (let token of queryTokens) { 552 | if (this.index["terms"].hasOwnProperty(token)) { 553 | // We've got a hit! 554 | for (const [docId, positions] of Object.entries(this.index["terms"][token])) { 555 | // Check if we've already seen the document. Initialize a result 556 | // if not. 557 | if (!initialResults.hasOwnProperty(docId)) { 558 | initialResults[docId] = { 559 | "terms": {}, 560 | "length": this.index["documentIds"][docId], 561 | "score": 0, 562 | }; 563 | } 564 | 565 | if (!initialResults[docId]["terms"].hasOwnProperty(token)) { 566 | initialResults[docId]["terms"][token] = 0; 567 | } 568 | 569 | // FIXME: We're still using a count here, when we can do better. 570 | initialResults[docId]["terms"][token] += positions.length; 571 | } 572 | } 573 | } 574 | } 575 | 576 | // Score the results. 577 | for (let [docId, rawResult] of Object.entries(initialResults)) { 578 | // Generate a score for the result. 579 | let score = this.scoreResult(rawResult); 580 | rawResults.push({ 581 | "docId": docId, 582 | "score": score, 583 | }); 584 | } 585 | 586 | return rawResults; 587 | } 588 | 589 | /** 590 | * Scores a raw result. 591 | * 592 | * Mostly an internal method called by `_search`, but you can override if 593 | * needed/have a better way to score. 594 | * @param {object} rawResult - The raw result to score. 595 | * @return {float} 596 | */ 597 | scoreResult(rawResult) { 598 | // For now, we'll simply calculate how much of the overall document length 599 | // is consumed by the total token length. 600 | let totalTokenLength = 0; 601 | 602 | for (let [token, count] of Object.entries(rawResult["terms"])) { 603 | totalTokenLength += token.length * count; 604 | } 605 | 606 | return totalTokenLength / rawResult["length"]; 607 | } 608 | 609 | /** 610 | * Performs a search against the index & returns results. 611 | * @param {string} query - The user's query to search on. 612 | * @return {Results} 613 | */ 614 | search(query) { 615 | const allResults = this._search(query); 616 | 617 | // Reorder all the results by score. 618 | allResults.sort((a, b) => { 619 | if (a["score"] > b["score"]) { 620 | return -1; 621 | } else if (a["score"] < b["score"]) { 622 | return 1; 623 | } 624 | 625 | return 0; 626 | }); 627 | 628 | return new Results(query, allResults); 629 | } 630 | 631 | /** 632 | * Dumps the index to a JSON string. 633 | * 634 | * Useful for persisting an already-built index. 635 | * @return {string} 636 | */ 637 | toJson() { 638 | return JSON.stringify(this.index); 639 | } 640 | 641 | /** 642 | * Loads the index from a JSON string. 643 | * 644 | * This *clears* the index first! 645 | * @param {string} indexData - The JSON of a previously-built index. 646 | * @return {undefined} 647 | */ 648 | fromJson(indexData) { 649 | const loadedData = JSON.parse(indexData); 650 | 651 | if (!loadedData.hasOwnProperty("version")) { 652 | throw new Error("Index data has no version information! Aborting load."); 653 | } 654 | 655 | // Do a basic version check. 656 | const ourVersion = VERSION.split(".", 1).toString(); 657 | const loadedVersion = loadedData["version"].split(".", 1).toString(); 658 | 659 | if (ourVersion !== loadedVersion) { 660 | throw new Error("Index major version doesn't match! Aborting load."); 661 | } 662 | 663 | this.clear(); 664 | this.index = loadedData; 665 | } 666 | } 667 | 668 | export { 669 | VERSION, 670 | ENGLISH_STOP_WORDS, 671 | TermPosition, 672 | BasicPreprocessor, 673 | EnglishPreprocessor, 674 | NGramTokenizer, 675 | RegExpTokenizer, 676 | SearchEngine, 677 | }; 678 | -------------------------------------------------------------------------------- /test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Test NanoSearch 5 | 39 | 40 | 41 | 42 |

Test NanoSearch

43 | 44 |
    45 |
  1. 46 | Run me via `python3 -m http.server 8888`. 47 |
  2. 48 |
  3. 49 | Open http://localhost:8888/ 50 | in your browser. 51 |
  4. 52 |
  5. 53 | Open the Javascript console. 54 |
  6. 55 |
  7. 56 | The search engine has a small index built & is available 57 | via `window.engine`. 58 |
  8. 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /test/nanosearch.test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | 3 | import { 4 | VERSION, 5 | TermPosition, 6 | BasicPreprocessor, 7 | EnglishPreprocessor, 8 | NGramTokenizer, 9 | RegExpTokenizer, 10 | SearchEngine, 11 | } from "../src/index.js"; 12 | 13 | describe("BasicPreprocessor", function () { 14 | describe("default processing", function () { 15 | it("properly cleans a phrase of symbols", function () { 16 | const pp = new BasicPreprocessor(); 17 | const doc = "The \"dog\" is -a 'hot dog'."; 18 | 19 | let cleaned = pp.clean(doc); 20 | assert.equal(cleaned, "The dog is a hot dog"); 21 | }); 22 | 23 | it("properly handles Unicode", function () { 24 | const pp = new BasicPreprocessor(); 25 | const ukrainian = "Собака коричнева."; 26 | const serbian = "Пас је браон."; 27 | const japanese = "犬は茶色です。"; 28 | let terms; 29 | 30 | terms = pp.process(ukrainian); 31 | assert.equal(terms.length, 2); 32 | assert.equal(terms[0].term, "собака"); 33 | assert.equal(terms[1].term, "коричнева"); 34 | 35 | terms = pp.process(serbian); 36 | assert.equal(terms.length, 3); 37 | assert.equal(terms[0].term, "пас"); 38 | assert.equal(terms[1].term, "је"); 39 | assert.equal(terms[2].term, "браон"); 40 | 41 | // I'm sure this is incorrect, but would work with a better n-gram size. 42 | // For now, it's enough that it's configurable & preserves the characters. 43 | terms = pp.process(japanese); 44 | assert.equal(terms.length, 1); 45 | assert.equal(terms[0].term, "犬は茶色です。"); 46 | }); 47 | 48 | it("returns the correct terms", function () { 49 | const pp = new BasicPreprocessor(); 50 | const doc = "The dog is hot."; 51 | 52 | const terms = pp.process(doc); 53 | assert.equal(terms.length, 4); 54 | assert.equal(terms[0].term, "the"); 55 | assert.equal(terms[0].position, 0); 56 | assert.equal(terms[1].term, "dog"); 57 | assert.equal(terms[1].position, 4); 58 | assert.equal(terms[2].term, "is"); 59 | assert.equal(terms[2].position, 8); 60 | assert.equal(terms[3].term, "hot"); 61 | assert.equal(terms[3].position, 11); 62 | }); 63 | 64 | it("returns the correct terms with surrounding whitespace", function () { 65 | const pp = new BasicPreprocessor(); 66 | const doc = " The dog is hot.\n "; 67 | 68 | const terms = pp.process(doc); 69 | assert.equal(terms.length, 4); 70 | assert.equal(terms[0].term, "the"); 71 | assert.equal(terms[0].position, 1); 72 | assert.equal(terms[1].term, "dog"); 73 | assert.equal(terms[1].position, 5); 74 | assert.equal(terms[2].term, "is"); 75 | assert.equal(terms[2].position, 9); 76 | assert.equal(terms[3].term, "hot"); 77 | assert.equal(terms[3].position, 12); 78 | }); 79 | }); 80 | 81 | describe("overridden processing", function () { 82 | it("correctly handles different splits", function () { 83 | const pp = new BasicPreprocessor(/[\n]/); 84 | const doc = "The dog\nis hot.\n\n"; 85 | 86 | const terms = pp.process(doc); 87 | assert.equal(terms.length, 2); 88 | assert.equal(terms[0].term, "the dog"); 89 | assert.equal(terms[0].position, 0); 90 | assert.equal(terms[1].term, "is hot"); 91 | assert.equal(terms[1].position, 8); 92 | }); 93 | 94 | it("avoids stop words", function () { 95 | const pp = new BasicPreprocessor(null, ["the", "a", "an", "is"]); 96 | const doc = "The dog is a 'hot dog'."; 97 | 98 | const terms = pp.process(doc); 99 | assert.equal(terms.length, 3); 100 | assert.equal(terms[0].term, "dog"); 101 | assert.equal(terms[0].position, 4); 102 | assert.equal(terms[1].term, "hot"); 103 | assert.equal(terms[1].position, 13); 104 | assert.equal(terms[2].term, "dog"); 105 | assert.equal(terms[2].position, 18); 106 | }); 107 | 108 | it("reduced punctuation replacement", function () { 109 | // This keeps the various quotes (normally stripped), but removes the 110 | // dash & period. 111 | const pp = new BasicPreprocessor(null, null, /[\.\-]/g); 112 | const doc = "The \"dog\" is -a 'hot dog'."; 113 | 114 | const terms = pp.process(doc); 115 | assert.equal(terms.length, 6); 116 | assert.equal(terms[0].term, "the"); 117 | assert.equal(terms[0].position, 0); 118 | assert.equal(terms[1].term, '"dog"'); 119 | assert.equal(terms[1].position, 4); 120 | assert.equal(terms[2].term, "is"); 121 | assert.equal(terms[2].position, 10); 122 | assert.equal(terms[3].term, "a"); 123 | assert.equal(terms[3].position, 13); 124 | assert.equal(terms[4].term, "'hot"); 125 | assert.equal(terms[4].position, 16); 126 | assert.equal(terms[5].term, "dog'"); 127 | assert.equal(terms[5].position, 21); 128 | }); 129 | }); 130 | }); 131 | 132 | describe("EnglishPreprocessor", function () { 133 | describe("default processing", function () { 134 | it("avoids default stop words", function () { 135 | const pp = new EnglishPreprocessor(); 136 | const doc = "Our dog is a very good boy, but he isn't much of a swimmer."; 137 | 138 | const terms = pp.process(doc); 139 | assert.equal(terms.length, 5); 140 | assert.equal(terms[0].term, "dog"); 141 | assert.equal(terms[1].term, "good"); 142 | assert.equal(terms[2].term, "boy"); 143 | assert.equal(terms[3].term, "much"); 144 | assert.equal(terms[4].term, "swimmer"); 145 | }); 146 | }); 147 | }); 148 | 149 | describe("NGramTokenizer", function () { 150 | describe("default processing", function () { 151 | it("returns the correct n-grams", function () { 152 | const ng = new NGramTokenizer(); 153 | const word = "doggone"; 154 | 155 | const tokens = ng.tokenize(word); 156 | assert.equal(tokens.length, 5); 157 | assert.deepEqual(tokens, [ 158 | "dog", 159 | "ogg", 160 | "ggo", 161 | "gon", 162 | "one", 163 | ]); 164 | }); 165 | }); 166 | 167 | describe("overridden processing", function () { 168 | it("returns shorter n-grams", function () { 169 | const ng = new NGramTokenizer(2); 170 | const word = "doggone"; 171 | 172 | const tokens = ng.tokenize(word); 173 | assert.equal(tokens.length, 6); 174 | assert.deepEqual(tokens, [ 175 | "do", 176 | "og", 177 | "gg", 178 | "go", 179 | "on", 180 | "ne", 181 | ]); 182 | }); 183 | }); 184 | }); 185 | 186 | describe("RegExpTokenizer", function () { 187 | describe("default processing", function () { 188 | it("returns the correct tokens", function () { 189 | const re = new RegExpTokenizer(); 190 | 191 | assert.deepEqual(re.tokenize("thinking"), ["think"]); 192 | assert.deepEqual(re.tokenize("thinks"), ["think"]); 193 | assert.deepEqual(re.tokenize("processed"), ["process"]); 194 | assert.deepEqual(re.tokenize("processes"), ["process"]); 195 | assert.deepEqual(re.tokenize("processable"), ["process"]); 196 | // This would be too short without the suffix, so we get the whole word. 197 | assert.deepEqual(re.tokenize("doing"), ["doing"]); 198 | }); 199 | }); 200 | }); 201 | 202 | describe("SearchEngine", function () { 203 | describe("constructor", function () { 204 | it("sets internal state", function () { 205 | const engine = new SearchEngine(); 206 | 207 | assert.deepEqual(engine.index, { 208 | "documentIds": {}, 209 | "terms": {}, 210 | "version": VERSION, 211 | }); 212 | }); 213 | }); 214 | 215 | describe("add", function () { 216 | it("correctly adds a document", function () { 217 | const engine = new SearchEngine(); 218 | 219 | engine.add("abc", "The dogs are loose"); 220 | 221 | assert.equal(Object.keys(engine.index["documentIds"]).length, 1); 222 | assert.equal(Object.keys(engine.index["terms"]).length, 7); 223 | }); 224 | }); 225 | 226 | describe("clear", function () { 227 | it("correctly empties the index", function () { 228 | const engine = new SearchEngine(); 229 | 230 | engine.add("abc", "The dogs are loose"); 231 | engine.add("def", "No cats aren't"); 232 | 233 | // Sanity check. 234 | assert.equal(Object.keys(engine.index["documentIds"]).length, 2); 235 | assert.equal(Object.keys(engine.index["terms"]).length, 12); 236 | 237 | engine.clear(); 238 | assert.equal(Object.keys(engine.index["documentIds"]).length, 0); 239 | assert.equal(Object.keys(engine.index["terms"]).length, 0); 240 | }); 241 | }); 242 | 243 | describe("remove", function () { 244 | it("correctly removes a document", function () { 245 | const engine = new SearchEngine(); 246 | 247 | engine.add("abc", "The dogs are loose"); 248 | engine.add("def", "No cats aren't"); 249 | 250 | assert.equal(Object.keys(engine.index["documentIds"]).length, 2); 251 | assert.equal(Object.keys(engine.index["terms"]).length, 12); 252 | 253 | engine.remove("def"); 254 | 255 | assert.equal(Object.keys(engine.index["documentIds"]).length, 1); 256 | assert.equal(Object.keys(engine.index["terms"]).length, 7); 257 | }); 258 | }); 259 | 260 | describe("search", function () { 261 | it("searches against the index", function () { 262 | const engine = new SearchEngine(); 263 | 264 | engine.add("abc", "The dog is a 'hot dog'."); 265 | engine.add("def", "Dogs > Cats"); 266 | engine.add("ghi", "the quick brown fox jumps over the lazy dog"); 267 | engine.add("jkl", "Am I lazy, or just work smart?"); 268 | 269 | let results = engine.search("my dog"); 270 | assert.equal(results.count(), 3); 271 | 272 | let iter = results.iterator(); 273 | let res = iter.next(); 274 | assert.equal(res.value.docId, "def"); 275 | assert.equal((res.value.score > 0) && (res.value.score < 1), true); 276 | assert.equal(res.done, false); 277 | 278 | res = iter.next(); 279 | assert.equal(res.value.docId, "abc"); 280 | assert.equal((res.value.score > 0) && (res.value.score < 1), true); 281 | assert.equal(res.done, false); 282 | 283 | res = iter.next(); 284 | assert.equal(res.value.docId, "ghi"); 285 | assert.equal((res.value.score > 0) && (res.value.score < 1), true); 286 | assert.equal(res.done, false); 287 | 288 | res = iter.next(); 289 | assert.equal(res.done, true); 290 | 291 | results = engine.search("lazy"); 292 | let topResult = results.at(0); 293 | assert.equal(topResult.docId, "jkl"); 294 | 295 | results = engine.search("dogs"); 296 | let pageOne = results.slice(0, 10); 297 | let pageTwo = results.slice(10, 20); 298 | 299 | assert.equal(pageOne.length, 3); 300 | assert.equal(pageOne[0].docId, "def"); 301 | assert.equal(pageOne[1].docId, "abc"); 302 | assert.equal(pageOne[2].docId, "ghi"); 303 | 304 | // There is no page two. 305 | assert.equal(pageTwo.length, 0); 306 | }); 307 | }); 308 | 309 | describe("toJson", function () { 310 | it("stores to JSON", function () { 311 | const engine = new SearchEngine(); 312 | 313 | engine.add("abc", "The dog is a 'hot dog'."); 314 | engine.add("def", "Dogs > Cats"); 315 | 316 | const index = engine.toJson(); 317 | 318 | // Due to object instability, we need to load it. 319 | const rawData = JSON.parse(index); 320 | assert.equal(Object.keys(rawData["documentIds"]).length, 2); 321 | assert.equal(Object.keys(rawData["terms"]).length, 8); 322 | assert.equal(rawData["version"], VERSION); 323 | }); 324 | }); 325 | 326 | describe("fromJson", function () { 327 | it("loads from JSON", function () { 328 | const engine = new SearchEngine(); 329 | 330 | // Sanity check. 331 | assert.equal(Object.keys(engine.index["documentIds"]).length, 0); 332 | assert.equal(Object.keys(engine.index["terms"]).length, 0); 333 | 334 | const rawData = { 335 | "version": VERSION, 336 | "documentIds": ["abc"], 337 | "terms": { 338 | "dog": { 339 | "abc": [1, 2, 3], 340 | }, 341 | }, 342 | }; 343 | 344 | const index = JSON.stringify(rawData); 345 | engine.fromJson(index); 346 | assert.equal(Object.keys(engine.index["documentIds"]).length, 1); 347 | assert.equal(Object.keys(engine.index["terms"]).length, 1); 348 | assert.equal(engine.index["version"], VERSION); 349 | 350 | // Sanity check. 351 | const results = engine.search("dog"); 352 | assert.equal(results.count(), 1); 353 | }); 354 | }); 355 | }); 356 | --------------------------------------------------------------------------------