diff --git a/.babelrc b/.babelrc deleted file mode 100644 index 738dfbe..0000000 --- a/.babelrc +++ /dev/null @@ -1,15 +0,0 @@ -{ - "env": { - "test": { - "plugins": ["@babel/plugin-transform-modules-commonjs"] - }, - "development": { - "presets": [["@babel/env"]], - "plugins": ["add-module-exports"] - }, - "production": { - "presets": [["@babel/env"], "minify"], - "plugins": ["add-module-exports"] - } - } -} diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index c1a910e..0000000 --- a/.editorconfig +++ /dev/null @@ -1,14 +0,0 @@ -# EditorConfig helps developers define and maintain -# consistent coding styles between different editors and IDEs. - -root = true - -[*] -end_of_line = lf -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true -indent_style = tab - -[*.md] -trim_trailing_whitespace = false diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100755 index 5a000bb..0000000 --- a/.eslintrc.js +++ /dev/null @@ -1,21 +0,0 @@ -module.exports = { - env: { - browser: true, - node: true, - es2021: true, - jest: true - }, - extends: ['eslint:recommended'], - parserOptions: { - ecmaVersion: 12, - sourceType: 'module' - }, - rules: { - quotes: ['error', 'single', { avoidEscape: true }], - semi: ['error', 'never'], - indent: 'off', - 'no-mixed-spaces-and-tabs': ['warn', 'smart-tabs'], - 'linebreak-style': ['error', 'unix'], - 'no-unused-vars': 'warn' - } -} diff --git a/.gitignore b/.gitignore index d5505e5..ea75346 100644 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,7 @@ jspm_packages .npm .node_repl_history .idea -lib +dist package-lock.json .DS_Store Thumbs.db diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..fa29cdf --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +** \ No newline at end of file diff --git a/.prettierrc b/.prettierrc deleted file mode 100755 index 67d0eb5..0000000 --- a/.prettierrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "trailingComma": "none", - "tabWidth": 4, - "useTabs": true, - "semi": false, - "singleQuote": true -} diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 64dbfec..5e9018d 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,3 +1,5 @@ { - "recommendations": ["esbenp.prettier-vscode"] -} + "recommendations": [ + "biomejs.biome" + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 0c4a68e..41368f0 100755 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,8 @@ { - "editor.formatOnSave": true, - "files.insertFinalNewline": true, - "editor.defaultFormatter": "esbenp.prettier-vscode", - "prettier.useTabs": true + "editor.formatOnSave": true, + "files.insertFinalNewline": true, + "editor.defaultFormatter": "biomejs.biome", + "[typescript]": { + "editor.defaultFormatter": "biomejs.biome" + } } diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..277b175 --- /dev/null +++ b/biome.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.6.3/schema.json", + "organizeImports": { + "enabled": true + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "style": { + "noParameterAssign": "off" + } + } + }, + "files": { + "ignore": ["dist/*"] + }, + "javascript": { + "formatter": { + "quoteStyle": "single" + } + } +} diff --git a/package.json b/package.json index 429a9d9..87fcee0 100644 --- a/package.json +++ b/package.json @@ -1,21 +1,26 @@ { "name": "ml-classify-text", - "version": "2.0.1", + "version": "3.0.0", "description": "Text classification using n-grams and cosine similarity", - "module": "./lib", - "main": "./lib", + "type": "module", + "main": "./dist/index.cjs", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "require": "./dist/index.cjs", + "import": "./dist/index.js", + "types": "./dist/index.d.ts" + } + }, "scripts": { - "clean": "rimraf lib", - "test": "jest --coverage", - "test:watch": "jest --watchAll", - "test:prod": "cross-env BABEL_ENV=production npm run test", - "lint": "eslint src test", - "build": "webpack --mode=production --config=webpack.config.js", - "prepublish": "npm run clean && npm run lint && npm run test && npm run build" + "dev": "vitest", + "test": "vitest run", + "lint": "biome lint src test", + "build": "tsup src/index.ts --format esm,cjs --dts --clean", + "prepublish": "npm run lint && npm run test && npm run build", + "format": "biome check --apply ." }, - "files": [ - "lib" - ], "repository": { "type": "git", "url": "git+https://github.com/andreekeberg/ml-classify-text-js.git" @@ -50,34 +55,12 @@ }, "homepage": "https://github.com/andreekeberg/ml-classify-text-js", "devDependencies": { - "@babel/core": "^7.20.12", - "@babel/plugin-transform-modules-amd": "^7.20.11", - "@babel/plugin-transform-modules-commonjs": "^7.20.11", - "@babel/plugin-transform-runtime": "^7.19.6", - "@babel/polyfill": "^7.12.1", - "@babel/preset-env": "^7.20.2", - "@babel/register": "^7.18.9", - "@babel/runtime": "^7.20.13", - "@babel/runtime-corejs3": "^7.20.13", - "babel-cli": "^6.26.0", - "babel-eslint": "^10.1.0", - "babel-loader": "^9.1.2", - "babel-plugin-add-module-exports": "^1.0.4", - "babel-polyfill": "^6.26.0", - "babel-preset-env": "^1.7.0", - "babel-preset-minify": "^0.5.2", - "babel-runtime": "^6.26.0", - "core-js": "^3.27.2", - "cross-env": "^7.0.3", - "eslint": "^8.33.0", - "eslint-config-standard": "^17.0.0", - "eslint-plugin-node": "^11.1.0", - "jest": "^29.4.1", - "jsdoc": "^4.0.0", - "jsdoc-to-markdown": "^8.0.0", - "rimraf": "^4.1.2", - "webpack": "^5.75.0", - "webpack-cli": "^5.0.1" + "@biomejs/biome": "1.6.3", + "jsdoc": "^4.0.2", + "jsdoc-to-markdown": "^8.0.1", + "tsup": "^8.0.2", + "typescript": "^5.4.3", + "vitest": "^1.4.0" }, "dependencies": { "xregexp": "^5.1.1" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..21e1840 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,2559 @@ +lockfileVersion: '6.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +dependencies: + xregexp: + specifier: ^5.1.1 + version: 5.1.1 + +devDependencies: + '@biomejs/biome': + specifier: 1.6.3 + version: 1.6.3 + jsdoc: + specifier: ^4.0.2 + version: 4.0.2 + jsdoc-to-markdown: + specifier: ^8.0.1 + version: 8.0.1 + tsup: + specifier: ^8.0.2 + version: 8.0.2(typescript@5.4.3) + typescript: + specifier: ^5.4.3 + version: 5.4.3 + vitest: + specifier: ^1.4.0 + version: 1.4.0 + +packages: + + /@babel/helper-string-parser@7.24.1: + resolution: {integrity: sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==} + engines: {node: '>=6.9.0'} + dev: true + + /@babel/helper-validator-identifier@7.22.20: + resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==} + engines: {node: '>=6.9.0'} + dev: true + + /@babel/parser@7.24.4: + resolution: {integrity: sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg==} + engines: {node: '>=6.0.0'} + hasBin: true + dependencies: + '@babel/types': 7.24.0 + dev: true + + /@babel/runtime-corejs3@7.24.4: + resolution: {integrity: sha512-VOQOexSilscN24VEY810G/PqtpFvx/z6UqDIjIWbDe2368HhDLkYN5TYwaEz/+eRCUkhJ2WaNLLmQAlxzfWj4w==} + engines: {node: '>=6.9.0'} + dependencies: + core-js-pure: 3.36.1 + regenerator-runtime: 0.14.1 + dev: false + + /@babel/types@7.24.0: + resolution: {integrity: sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-string-parser': 7.24.1 + '@babel/helper-validator-identifier': 7.22.20 + to-fast-properties: 2.0.0 + dev: true + + /@biomejs/biome@1.6.3: + resolution: {integrity: sha512-Xnp/TIpIcTnRA4LwerJuoGYQJEqwXtn5AL0U0OPXll/QGbAKmcUAfizU880xTwZRD4f53iceqODLDaD3wxYlIw==} + engines: {node: '>=14.*'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@biomejs/cli-darwin-arm64': 1.6.3 + '@biomejs/cli-darwin-x64': 1.6.3 + '@biomejs/cli-linux-arm64': 1.6.3 + '@biomejs/cli-linux-arm64-musl': 1.6.3 + '@biomejs/cli-linux-x64': 1.6.3 + '@biomejs/cli-linux-x64-musl': 1.6.3 + '@biomejs/cli-win32-arm64': 1.6.3 + '@biomejs/cli-win32-x64': 1.6.3 + dev: true + + /@biomejs/cli-darwin-arm64@1.6.3: + resolution: {integrity: sha512-0E8PGu3/8HSkBJdtjno+niJE1ANS/12D7sPK65vw5lTBYmmaYwJdfclDp6XO0IAX7uVd3/YtXlsEua0SVrNt3Q==} + engines: {node: '>=14.*'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@biomejs/cli-darwin-x64@1.6.3: + resolution: {integrity: sha512-UWu0We/aIRtWXgJKe6ygWt2xR0yXs64BwWqtZbfxBojRn3jgW8UdFAkV5yiUOX3TQlsV6BZH1EQaUAVsccUeeA==} + engines: {node: '>=14.*'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@biomejs/cli-linux-arm64-musl@1.6.3: + resolution: {integrity: sha512-AntGCSfLN1nPcQj4VOk3X2JgnDw07DaPC8BuBmRcsRmn+7GPSWLllVN5awIKlRPZEbGJtSnLkTiDc5Bxw8OiuA==} + engines: {node: '>=14.*'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@biomejs/cli-linux-arm64@1.6.3: + resolution: {integrity: sha512-wFVkQw38kOssfnkbpSh6ums5TaElw3RAt5i/VZwHmgR2nQgE0fHXLO7HwIE9VBkOEdbiIFq+2PxvFIHuJF3z3Q==} + engines: {node: '>=14.*'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@biomejs/cli-linux-x64-musl@1.6.3: + resolution: {integrity: sha512-GelAvGsUwbxfFpKLG+7+dvDmbrfkGqn08sL8CMQrGnhjE1krAqHWiXQsjfmi0UMFdMsk7hbc4oSAP+1+mrXcHQ==} + engines: {node: '>=14.*'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@biomejs/cli-linux-x64@1.6.3: + resolution: {integrity: sha512-vyn8TQaTZg617hjqFitwGmb1St5XXvq6I3vmxU/QFalM74BryMSvYCrYWb2Yw/TkykdEwZTMGYp+SWHRb04fTg==} + engines: {node: '>=14.*'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@biomejs/cli-win32-arm64@1.6.3: + resolution: {integrity: sha512-Gx8N2Tixke6pAI1BniteCVZgUUmaFEDYosdWxoaCus15BZI/7RcBxhsRM0ZL/lC66StSQ8vHl8JBrrld1k570Q==} + engines: {node: '>=14.*'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@biomejs/cli-win32-x64@1.6.3: + resolution: {integrity: sha512-meungPJw64SqoR7LXY1wG7GC4+4wgpyThdFUMGXa6PCe0BLFOIOcZ9VMj9PstuczMPdgmt/BUMPsj25dK1VO8A==} + engines: {node: '>=14.*'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/aix-ppc64@0.19.12: + resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + requiresBuild: true + dev: true + optional: true + + /@esbuild/aix-ppc64@0.20.2: + resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm64@0.19.12: + resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm64@0.20.2: + resolution: {integrity: sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm@0.19.12: + resolution: {integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm@0.20.2: + resolution: {integrity: sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-x64@0.19.12: + resolution: {integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-x64@0.20.2: + resolution: {integrity: sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-arm64@0.19.12: + resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-arm64@0.20.2: + resolution: {integrity: sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-x64@0.19.12: + resolution: {integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-x64@0.20.2: + resolution: {integrity: sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-arm64@0.19.12: + resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-arm64@0.20.2: + resolution: {integrity: sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-x64@0.19.12: + resolution: {integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-x64@0.20.2: + resolution: {integrity: sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm64@0.19.12: + resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm64@0.20.2: + resolution: {integrity: sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm@0.19.12: + resolution: {integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm@0.20.2: + resolution: {integrity: sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ia32@0.19.12: + resolution: {integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ia32@0.20.2: + resolution: {integrity: sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-loong64@0.19.12: + resolution: {integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-loong64@0.20.2: + resolution: {integrity: sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-mips64el@0.19.12: + resolution: {integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-mips64el@0.20.2: + resolution: {integrity: sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ppc64@0.19.12: + resolution: {integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ppc64@0.20.2: + resolution: {integrity: sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-riscv64@0.19.12: + resolution: {integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-riscv64@0.20.2: + resolution: {integrity: sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-s390x@0.19.12: + resolution: {integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-s390x@0.20.2: + resolution: {integrity: sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-x64@0.19.12: + resolution: {integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-x64@0.20.2: + resolution: {integrity: sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/netbsd-x64@0.19.12: + resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/netbsd-x64@0.20.2: + resolution: {integrity: sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/openbsd-x64@0.19.12: + resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/openbsd-x64@0.20.2: + resolution: {integrity: sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/sunos-x64@0.19.12: + resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true + + /@esbuild/sunos-x64@0.20.2: + resolution: {integrity: sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-arm64@0.19.12: + resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-arm64@0.20.2: + resolution: {integrity: sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-ia32@0.19.12: + resolution: {integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-ia32@0.20.2: + resolution: {integrity: sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-x64@0.19.12: + resolution: {integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-x64@0.20.2: + resolution: {integrity: sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@isaacs/cliui@8.0.2: + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + dependencies: + string-width: 5.1.2 + string-width-cjs: /string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: /strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: /wrap-ansi@7.0.0 + dev: true + + /@jest/schemas@29.6.3: + resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@sinclair/typebox': 0.27.8 + dev: true + + /@jridgewell/gen-mapping@0.3.5: + resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} + engines: {node: '>=6.0.0'} + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/trace-mapping': 0.3.25 + dev: true + + /@jridgewell/resolve-uri@3.1.2: + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + dev: true + + /@jridgewell/set-array@1.2.1: + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + dev: true + + /@jridgewell/sourcemap-codec@1.4.15: + resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + dev: true + + /@jridgewell/trace-mapping@0.3.25: + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.4.15 + dev: true + + /@jsdoc/salty@0.2.7: + resolution: {integrity: sha512-mh8LbS9d4Jq84KLw8pzho7XC2q2/IJGiJss3xwRoLD1A+EE16SjN4PfaG4jRCzKegTFLlN0Zd8SdUPE6XdoPFg==} + engines: {node: '>=v12.0.0'} + dependencies: + lodash: 4.17.21 + dev: true + + /@nodelib/fs.scandir@2.1.5: + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + dev: true + + /@nodelib/fs.stat@2.0.5: + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + dev: true + + /@nodelib/fs.walk@1.2.8: + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.17.1 + dev: true + + /@pkgjs/parseargs@0.11.0: + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-android-arm-eabi@4.14.0: + resolution: {integrity: sha512-jwXtxYbRt1V+CdQSy6Z+uZti7JF5irRKF8hlKfEnF/xJpcNGuuiZMBvuoYM+x9sr9iWGnzrlM0+9hvQ1kgkf1w==} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-android-arm64@4.14.0: + resolution: {integrity: sha512-fI9nduZhCccjzlsA/OuAwtFGWocxA4gqXGTLvOyiF8d+8o0fZUeSztixkYjcGq1fGZY3Tkq4yRvHPFxU+jdZ9Q==} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-darwin-arm64@4.14.0: + resolution: {integrity: sha512-BcnSPRM76/cD2gQC+rQNGBN6GStBs2pl/FpweW8JYuz5J/IEa0Fr4AtrPv766DB/6b2MZ/AfSIOSGw3nEIP8SA==} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-darwin-x64@4.14.0: + resolution: {integrity: sha512-LDyFB9GRolGN7XI6955aFeI3wCdCUszFWumWU0deHA8VpR3nWRrjG6GtGjBrQxQKFevnUTHKCfPR4IvrW3kCgQ==} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-arm-gnueabihf@4.14.0: + resolution: {integrity: sha512-ygrGVhQP47mRh0AAD0zl6QqCbNsf0eTo+vgwkY6LunBcg0f2Jv365GXlDUECIyoXp1kKwL5WW6rsO429DBY/bA==} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-arm64-gnu@4.14.0: + resolution: {integrity: sha512-x+uJ6MAYRlHGe9wi4HQjxpaKHPM3d3JjqqCkeC5gpnnI6OWovLdXTpfa8trjxPLnWKyBsSi5kne+146GAxFt4A==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-arm64-musl@4.14.0: + resolution: {integrity: sha512-nrRw8ZTQKg6+Lttwqo6a2VxR9tOroa2m91XbdQ2sUUzHoedXlsyvY1fN4xWdqz8PKmf4orDwejxXHjh7YBGUCA==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-powerpc64le-gnu@4.14.0: + resolution: {integrity: sha512-xV0d5jDb4aFu84XKr+lcUJ9y3qpIWhttO3Qev97z8DKLXR62LC3cXT/bMZXrjLF9X+P5oSmJTzAhqwUbY96PnA==} + cpu: [ppc64le] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-riscv64-gnu@4.14.0: + resolution: {integrity: sha512-SDDhBQwZX6LPRoPYjAZWyL27LbcBo7WdBFWJi5PI9RPCzU8ijzkQn7tt8NXiXRiFMJCVpkuMkBf4OxSxVMizAw==} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-s390x-gnu@4.14.0: + resolution: {integrity: sha512-RxB/qez8zIDshNJDufYlTT0ZTVut5eCpAZ3bdXDU9yTxBzui3KhbGjROK2OYTTor7alM7XBhssgoO3CZ0XD3qA==} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-x64-gnu@4.14.0: + resolution: {integrity: sha512-C6y6z2eCNCfhZxT9u+jAM2Fup89ZjiG5pIzZIDycs1IwESviLxwkQcFRGLjnDrP+PT+v5i4YFvlcfAs+LnreXg==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-x64-musl@4.14.0: + resolution: {integrity: sha512-i0QwbHYfnOMYsBEyjxcwGu5SMIi9sImDVjDg087hpzXqhBSosxkE7gyIYFHgfFl4mr7RrXksIBZ4DoLoP4FhJg==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-win32-arm64-msvc@4.14.0: + resolution: {integrity: sha512-Fq52EYb0riNHLBTAcL0cun+rRwyZ10S9vKzhGKKgeD+XbwunszSY0rVMco5KbOsTlwovP2rTOkiII/fQ4ih/zQ==} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-win32-ia32-msvc@4.14.0: + resolution: {integrity: sha512-e/PBHxPdJ00O9p5Ui43+vixSgVf4NlLsmV6QneGERJ3lnjIua/kim6PRFe3iDueT1rQcgSkYP8ZBBXa/h4iPvw==} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-win32-x64-msvc@4.14.0: + resolution: {integrity: sha512-aGg7iToJjdklmxlUlJh/PaPNa4PmqHfyRMLunbL3eaMO0gp656+q1zOKkpJ/CVe9CryJv6tAN1HDoR8cNGzkag==} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@sinclair/typebox@0.27.8: + resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + dev: true + + /@types/estree@1.0.5: + resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} + dev: true + + /@types/linkify-it@3.0.5: + resolution: {integrity: sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==} + dev: true + + /@types/markdown-it@12.2.3: + resolution: {integrity: sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==} + dependencies: + '@types/linkify-it': 3.0.5 + '@types/mdurl': 1.0.5 + dev: true + + /@types/mdurl@1.0.5: + resolution: {integrity: sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==} + dev: true + + /@vitest/expect@1.4.0: + resolution: {integrity: sha512-Jths0sWCJZ8BxjKe+p+eKsoqev1/T8lYcrjavEaz8auEJ4jAVY0GwW3JKmdVU4mmNPLPHixh4GNXP7GFtAiDHA==} + dependencies: + '@vitest/spy': 1.4.0 + '@vitest/utils': 1.4.0 + chai: 4.4.1 + dev: true + + /@vitest/runner@1.4.0: + resolution: {integrity: sha512-EDYVSmesqlQ4RD2VvWo3hQgTJ7ZrFQ2VSJdfiJiArkCerDAGeyF1i6dHkmySqk573jLp6d/cfqCN+7wUB5tLgg==} + dependencies: + '@vitest/utils': 1.4.0 + p-limit: 5.0.0 + pathe: 1.1.2 + dev: true + + /@vitest/snapshot@1.4.0: + resolution: {integrity: sha512-saAFnt5pPIA5qDGxOHxJ/XxhMFKkUSBJmVt5VgDsAqPTX6JP326r5C/c9UuCMPoXNzuudTPsYDZCoJ5ilpqG2A==} + dependencies: + magic-string: 0.30.8 + pathe: 1.1.2 + pretty-format: 29.7.0 + dev: true + + /@vitest/spy@1.4.0: + resolution: {integrity: sha512-Ywau/Qs1DzM/8Uc+yA77CwSegizMlcgTJuYGAi0jujOteJOUf1ujunHThYo243KG9nAyWT3L9ifPYZ5+As/+6Q==} + dependencies: + tinyspy: 2.2.1 + dev: true + + /@vitest/utils@1.4.0: + resolution: {integrity: sha512-mx3Yd1/6e2Vt/PUC98DcqTirtfxUyAZ32uK82r8rZzbtBeBo+nqgnjx/LvqQdWsrvNtm14VmurNgcf4nqY5gJg==} + dependencies: + diff-sequences: 29.6.3 + estree-walker: 3.0.3 + loupe: 2.3.7 + pretty-format: 29.7.0 + dev: true + + /acorn-walk@8.3.2: + resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==} + engines: {node: '>=0.4.0'} + dev: true + + /acorn@8.11.3: + resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==} + engines: {node: '>=0.4.0'} + hasBin: true + dev: true + + /ansi-escape-sequences@4.1.0: + resolution: {integrity: sha512-dzW9kHxH011uBsidTXd14JXgzye/YLb2LzeKZ4bsgl/Knwx8AtbSFkkGxagdNOoh0DlqHCmfiEjWKBaqjOanVw==} + engines: {node: '>=8.0.0'} + dependencies: + array-back: 3.1.0 + dev: true + + /ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + dev: true + + /ansi-regex@6.0.1: + resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} + engines: {node: '>=12'} + dev: true + + /ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + dependencies: + color-convert: 2.0.1 + dev: true + + /ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + dev: true + + /ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + dev: true + + /any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + dev: true + + /anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + dev: true + + /argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + dev: true + + /array-back@1.0.4: + resolution: {integrity: sha512-1WxbZvrmyhkNoeYcizokbmh5oiOCIfyvGtcqbK3Ls1v1fKcquzxnQSceOx6tzq7jmai2kFLWIpGND2cLhH6TPw==} + engines: {node: '>=0.12.0'} + dependencies: + typical: 2.6.1 + dev: true + + /array-back@2.0.0: + resolution: {integrity: sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==} + engines: {node: '>=4'} + dependencies: + typical: 2.6.1 + dev: true + + /array-back@3.1.0: + resolution: {integrity: sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==} + engines: {node: '>=6'} + dev: true + + /array-back@4.0.2: + resolution: {integrity: sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==} + engines: {node: '>=8'} + dev: true + + /array-back@5.0.0: + resolution: {integrity: sha512-kgVWwJReZWmVuWOQKEOohXKJX+nD02JAZ54D1RRWlv8L0NebauKAaFxACKzB74RTclt1+WNz5KHaLRDAPZbDEw==} + engines: {node: '>=10'} + dev: true + + /array-back@6.2.2: + resolution: {integrity: sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw==} + engines: {node: '>=12.17'} + dev: true + + /array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + dev: true + + /assertion-error@1.1.0: + resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} + dev: true + + /balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + dev: true + + /binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + dev: true + + /bluebird@3.7.2: + resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} + dev: true + + /brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + dev: true + + /brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + dependencies: + balanced-match: 1.0.2 + dev: true + + /braces@3.0.2: + resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} + engines: {node: '>=8'} + dependencies: + fill-range: 7.0.1 + dev: true + + /bundle-require@4.0.2(esbuild@0.19.12): + resolution: {integrity: sha512-jwzPOChofl67PSTW2SGubV9HBQAhhR2i6nskiOThauo9dzwDUgOWQScFVaJkjEfYX+UXiD+LEx8EblQMc2wIag==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + peerDependencies: + esbuild: '>=0.17' + dependencies: + esbuild: 0.19.12 + load-tsconfig: 0.2.5 + dev: true + + /cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + dev: true + + /cache-point@2.0.0: + resolution: {integrity: sha512-4gkeHlFpSKgm3vm2gJN5sPqfmijYRFYCQ6tv5cLw0xVmT6r1z1vd4FNnpuOREco3cBs1G709sZ72LdgddKvL5w==} + engines: {node: '>=8'} + dependencies: + array-back: 4.0.2 + fs-then-native: 2.0.0 + mkdirp2: 1.0.5 + dev: true + + /catharsis@0.9.0: + resolution: {integrity: sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==} + engines: {node: '>= 10'} + dependencies: + lodash: 4.17.21 + dev: true + + /chai@4.4.1: + resolution: {integrity: sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==} + engines: {node: '>=4'} + dependencies: + assertion-error: 1.1.0 + check-error: 1.0.3 + deep-eql: 4.1.3 + get-func-name: 2.0.2 + loupe: 2.3.7 + pathval: 1.1.1 + type-detect: 4.0.8 + dev: true + + /check-error@1.0.3: + resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} + dependencies: + get-func-name: 2.0.2 + dev: true + + /chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + dependencies: + anymatch: 3.1.3 + braces: 3.0.2 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + dev: true + + /collect-all@1.0.4: + resolution: {integrity: sha512-RKZhRwJtJEP5FWul+gkSMEnaK6H3AGPTTWOiRimCcs+rc/OmQE3Yhy1Q7A7KsdkG3ZXVdZq68Y6ONSdvkeEcKA==} + engines: {node: '>=0.10.0'} + dependencies: + stream-connect: 1.0.2 + stream-via: 1.0.4 + dev: true + + /color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + dependencies: + color-name: 1.1.4 + dev: true + + /color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + dev: true + + /command-line-args@5.2.1: + resolution: {integrity: sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==} + engines: {node: '>=4.0.0'} + dependencies: + array-back: 3.1.0 + find-replace: 3.0.0 + lodash.camelcase: 4.3.0 + typical: 4.0.0 + dev: true + + /command-line-tool@0.8.0: + resolution: {integrity: sha512-Xw18HVx/QzQV3Sc5k1vy3kgtOeGmsKIqwtFFoyjI4bbcpSgnw2CWVULvtakyw4s6fhyAdI6soQQhXc2OzJy62g==} + engines: {node: '>=4.0.0'} + dependencies: + ansi-escape-sequences: 4.1.0 + array-back: 2.0.0 + command-line-args: 5.2.1 + command-line-usage: 4.1.0 + typical: 2.6.1 + dev: true + + /command-line-usage@4.1.0: + resolution: {integrity: sha512-MxS8Ad995KpdAC0Jopo/ovGIroV/m0KHwzKfXxKag6FHOkGsH8/lv5yjgablcRxCJJC0oJeUMuO/gmaq+Wq46g==} + engines: {node: '>=4.0.0'} + dependencies: + ansi-escape-sequences: 4.1.0 + array-back: 2.0.0 + table-layout: 0.4.5 + typical: 2.6.1 + dev: true + + /commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + dev: true + + /common-sequence@2.0.2: + resolution: {integrity: sha512-jAg09gkdkrDO9EWTdXfv80WWH3yeZl5oT69fGfedBNS9pXUKYInVJ1bJ+/ht2+Moeei48TmSbQDYMc8EOx9G0g==} + engines: {node: '>=8'} + dev: true + + /concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + dev: true + + /config-master@3.1.0: + resolution: {integrity: sha512-n7LBL1zBzYdTpF1mx5DNcZnZn05CWIdsdvtPL4MosvqbBUK3Rq6VWEtGUuF3Y0s9/CIhMejezqlSkP6TnCJ/9g==} + dependencies: + walk-back: 2.0.1 + dev: true + + /core-js-pure@3.36.1: + resolution: {integrity: sha512-NXCvHvSVYSrewP0L5OhltzXeWFJLo2AL2TYnj6iLV3Bw8mM62wAQMNgUCRI6EBu6hVVpbCxmOPlxh1Ikw2PfUA==} + requiresBuild: true + dev: false + + /cross-spawn@7.0.3: + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + engines: {node: '>= 8'} + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + dev: true + + /debug@4.3.4: + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.2 + dev: true + + /deep-eql@4.1.3: + resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==} + engines: {node: '>=6'} + dependencies: + type-detect: 4.0.8 + dev: true + + /deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + dev: true + + /diff-sequences@29.6.3: + resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dev: true + + /dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + dependencies: + path-type: 4.0.0 + dev: true + + /dmd@6.2.0: + resolution: {integrity: sha512-uXWxLF1H7TkUAuoHK59/h/ts5cKavm2LnhrIgJWisip4BVzPoXavlwyoprFFn2CzcahKYgvkfaebS6oxzgflkg==} + engines: {node: '>=12'} + dependencies: + array-back: 6.2.2 + cache-point: 2.0.0 + common-sequence: 2.0.2 + file-set: 4.0.2 + handlebars: 4.7.8 + marked: 4.3.0 + object-get: 2.1.1 + reduce-flatten: 3.0.1 + reduce-unique: 2.0.1 + reduce-without: 1.0.1 + test-value: 3.0.0 + walk-back: 5.1.0 + dev: true + + /eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + dev: true + + /emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + dev: true + + /emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + dev: true + + /entities@2.1.0: + resolution: {integrity: sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==} + dev: true + + /esbuild@0.19.12: + resolution: {integrity: sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/aix-ppc64': 0.19.12 + '@esbuild/android-arm': 0.19.12 + '@esbuild/android-arm64': 0.19.12 + '@esbuild/android-x64': 0.19.12 + '@esbuild/darwin-arm64': 0.19.12 + '@esbuild/darwin-x64': 0.19.12 + '@esbuild/freebsd-arm64': 0.19.12 + '@esbuild/freebsd-x64': 0.19.12 + '@esbuild/linux-arm': 0.19.12 + '@esbuild/linux-arm64': 0.19.12 + '@esbuild/linux-ia32': 0.19.12 + '@esbuild/linux-loong64': 0.19.12 + '@esbuild/linux-mips64el': 0.19.12 + '@esbuild/linux-ppc64': 0.19.12 + '@esbuild/linux-riscv64': 0.19.12 + '@esbuild/linux-s390x': 0.19.12 + '@esbuild/linux-x64': 0.19.12 + '@esbuild/netbsd-x64': 0.19.12 + '@esbuild/openbsd-x64': 0.19.12 + '@esbuild/sunos-x64': 0.19.12 + '@esbuild/win32-arm64': 0.19.12 + '@esbuild/win32-ia32': 0.19.12 + '@esbuild/win32-x64': 0.19.12 + dev: true + + /esbuild@0.20.2: + resolution: {integrity: sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/aix-ppc64': 0.20.2 + '@esbuild/android-arm': 0.20.2 + '@esbuild/android-arm64': 0.20.2 + '@esbuild/android-x64': 0.20.2 + '@esbuild/darwin-arm64': 0.20.2 + '@esbuild/darwin-x64': 0.20.2 + '@esbuild/freebsd-arm64': 0.20.2 + '@esbuild/freebsd-x64': 0.20.2 + '@esbuild/linux-arm': 0.20.2 + '@esbuild/linux-arm64': 0.20.2 + '@esbuild/linux-ia32': 0.20.2 + '@esbuild/linux-loong64': 0.20.2 + '@esbuild/linux-mips64el': 0.20.2 + '@esbuild/linux-ppc64': 0.20.2 + '@esbuild/linux-riscv64': 0.20.2 + '@esbuild/linux-s390x': 0.20.2 + '@esbuild/linux-x64': 0.20.2 + '@esbuild/netbsd-x64': 0.20.2 + '@esbuild/openbsd-x64': 0.20.2 + '@esbuild/sunos-x64': 0.20.2 + '@esbuild/win32-arm64': 0.20.2 + '@esbuild/win32-ia32': 0.20.2 + '@esbuild/win32-x64': 0.20.2 + dev: true + + /escape-string-regexp@2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: '>=8'} + dev: true + + /estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + dependencies: + '@types/estree': 1.0.5 + dev: true + + /execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + dependencies: + cross-spawn: 7.0.3 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + dev: true + + /execa@8.0.1: + resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} + engines: {node: '>=16.17'} + dependencies: + cross-spawn: 7.0.3 + get-stream: 8.0.1 + human-signals: 5.0.0 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.3.0 + onetime: 6.0.0 + signal-exit: 4.1.0 + strip-final-newline: 3.0.0 + dev: true + + /fast-glob@3.3.2: + resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} + engines: {node: '>=8.6.0'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.5 + dev: true + + /fastq@1.17.1: + resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + dependencies: + reusify: 1.0.4 + dev: true + + /file-set@4.0.2: + resolution: {integrity: sha512-fuxEgzk4L8waGXaAkd8cMr73Pm0FxOVkn8hztzUW7BAHhOGH90viQNXbiOsnecCWmfInqU6YmAMwxRMdKETceQ==} + engines: {node: '>=10'} + dependencies: + array-back: 5.0.0 + glob: 7.2.3 + dev: true + + /fill-range@7.0.1: + resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} + engines: {node: '>=8'} + dependencies: + to-regex-range: 5.0.1 + dev: true + + /find-replace@3.0.0: + resolution: {integrity: sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==} + engines: {node: '>=4.0.0'} + dependencies: + array-back: 3.1.0 + dev: true + + /foreground-child@3.1.1: + resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} + engines: {node: '>=14'} + dependencies: + cross-spawn: 7.0.3 + signal-exit: 4.1.0 + dev: true + + /fs-then-native@2.0.0: + resolution: {integrity: sha512-X712jAOaWXkemQCAmWeg5rOT2i+KOpWz1Z/txk/cW0qlOu2oQ9H61vc5w3X/iyuUEfq/OyaFJ78/cZAQD1/bgA==} + engines: {node: '>=4.0.0'} + dev: true + + /fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + dev: true + + /fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /get-func-name@2.0.2: + resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} + dev: true + + /get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + dev: true + + /get-stream@8.0.1: + resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} + engines: {node: '>=16'} + dev: true + + /glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + dependencies: + is-glob: 4.0.3 + dev: true + + /glob@10.3.12: + resolution: {integrity: sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + dependencies: + foreground-child: 3.1.1 + jackspeak: 2.3.6 + minimatch: 9.0.4 + minipass: 7.0.4 + path-scurry: 1.10.2 + dev: true + + /glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + dev: true + + /globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.2 + ignore: 5.3.1 + merge2: 1.4.1 + slash: 3.0.0 + dev: true + + /graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + dev: true + + /handlebars@4.7.8: + resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} + engines: {node: '>=0.4.7'} + hasBin: true + dependencies: + minimist: 1.2.8 + neo-async: 2.6.2 + source-map: 0.6.1 + wordwrap: 1.0.0 + optionalDependencies: + uglify-js: 3.17.4 + dev: true + + /human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + dev: true + + /human-signals@5.0.0: + resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} + engines: {node: '>=16.17.0'} + dev: true + + /ignore@5.3.1: + resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} + engines: {node: '>= 4'} + dev: true + + /inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + dev: true + + /inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + dev: true + + /is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + dependencies: + binary-extensions: 2.3.0 + dev: true + + /is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + dev: true + + /is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + dev: true + + /is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + dependencies: + is-extglob: 2.1.1 + dev: true + + /is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + dev: true + + /is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + dev: true + + /is-stream@3.0.0: + resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dev: true + + /isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + dev: true + + /jackspeak@2.3.6: + resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} + engines: {node: '>=14'} + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + dev: true + + /joycon@3.1.1: + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} + engines: {node: '>=10'} + dev: true + + /js-tokens@9.0.0: + resolution: {integrity: sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==} + dev: true + + /js2xmlparser@4.0.2: + resolution: {integrity: sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==} + dependencies: + xmlcreate: 2.0.4 + dev: true + + /jsdoc-api@8.0.0: + resolution: {integrity: sha512-Rnhor0suB1Ds1abjmFkFfKeD+kSMRN9oHMTMZoJVUrmtCGDwXty+sWMA9sa4xbe4UyxuPjhC7tavZ40mDKK6QQ==} + engines: {node: '>=12.17'} + dependencies: + array-back: 6.2.2 + cache-point: 2.0.0 + collect-all: 1.0.4 + file-set: 4.0.2 + fs-then-native: 2.0.0 + jsdoc: 4.0.2 + object-to-spawn-args: 2.0.1 + temp-path: 1.0.0 + walk-back: 5.1.0 + dev: true + + /jsdoc-parse@6.2.1: + resolution: {integrity: sha512-9viGRUUtWOk/G4V0+nQ6rfLucz5plxh5I74WbNSNm9h9NWugCDVX4jbG8hZP9QqKGpdTPDE+qJXzaYNos3wqTA==} + engines: {node: '>=12'} + dependencies: + array-back: 6.2.2 + lodash.omit: 4.5.0 + reduce-extract: 1.0.0 + sort-array: 4.1.5 + test-value: 3.0.0 + dev: true + + /jsdoc-to-markdown@8.0.1: + resolution: {integrity: sha512-qJfNJhkq2C26UYoOdj8L1yheTJlk1veCsxwRejRmj07XZKCn7oSkuPErx6+JoNi8afCaUKdIM5oUu0uF2/T8iw==} + engines: {node: '>=12.17'} + hasBin: true + dependencies: + array-back: 6.2.2 + command-line-tool: 0.8.0 + config-master: 3.1.0 + dmd: 6.2.0 + jsdoc-api: 8.0.0 + jsdoc-parse: 6.2.1 + walk-back: 5.1.0 + dev: true + + /jsdoc@4.0.2: + resolution: {integrity: sha512-e8cIg2z62InH7azBBi3EsSEqrKx+nUtAS5bBcYTSpZFA+vhNPyhv8PTFZ0WsjOPDj04/dOLlm08EDcQJDqaGQg==} + engines: {node: '>=12.0.0'} + hasBin: true + dependencies: + '@babel/parser': 7.24.4 + '@jsdoc/salty': 0.2.7 + '@types/markdown-it': 12.2.3 + bluebird: 3.7.2 + catharsis: 0.9.0 + escape-string-regexp: 2.0.0 + js2xmlparser: 4.0.2 + klaw: 3.0.0 + markdown-it: 12.3.2 + markdown-it-anchor: 8.6.7(@types/markdown-it@12.2.3)(markdown-it@12.3.2) + marked: 4.3.0 + mkdirp: 1.0.4 + requizzle: 0.2.4 + strip-json-comments: 3.1.1 + underscore: 1.13.6 + dev: true + + /jsonc-parser@3.2.1: + resolution: {integrity: sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==} + dev: true + + /klaw@3.0.0: + resolution: {integrity: sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==} + dependencies: + graceful-fs: 4.2.11 + dev: true + + /lilconfig@3.1.1: + resolution: {integrity: sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==} + engines: {node: '>=14'} + dev: true + + /lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + dev: true + + /linkify-it@3.0.3: + resolution: {integrity: sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==} + dependencies: + uc.micro: 1.0.6 + dev: true + + /load-tsconfig@0.2.5: + resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dev: true + + /local-pkg@0.5.0: + resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==} + engines: {node: '>=14'} + dependencies: + mlly: 1.6.1 + pkg-types: 1.0.3 + dev: true + + /lodash.camelcase@4.3.0: + resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} + dev: true + + /lodash.omit@4.5.0: + resolution: {integrity: sha512-XeqSp49hNGmlkj2EJlfrQFIzQ6lXdNro9sddtQzcJY8QaoC2GO0DT7xaIokHeyM+mIT0mPMlPvkYzg2xCuHdZg==} + dev: true + + /lodash.padend@4.6.1: + resolution: {integrity: sha512-sOQs2aqGpbl27tmCS1QNZA09Uqp01ZzWfDUoD+xzTii0E7dSQfRKcRetFwa+uXaxaqL+TKm7CgD2JdKP7aZBSw==} + dev: true + + /lodash.sortby@4.7.0: + resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} + dev: true + + /lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + dev: true + + /loupe@2.3.7: + resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} + dependencies: + get-func-name: 2.0.2 + dev: true + + /lru-cache@10.2.0: + resolution: {integrity: sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==} + engines: {node: 14 || >=16.14} + dev: true + + /magic-string@0.30.8: + resolution: {integrity: sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==} + engines: {node: '>=12'} + dependencies: + '@jridgewell/sourcemap-codec': 1.4.15 + dev: true + + /markdown-it-anchor@8.6.7(@types/markdown-it@12.2.3)(markdown-it@12.3.2): + resolution: {integrity: sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==} + peerDependencies: + '@types/markdown-it': '*' + markdown-it: '*' + dependencies: + '@types/markdown-it': 12.2.3 + markdown-it: 12.3.2 + dev: true + + /markdown-it@12.3.2: + resolution: {integrity: sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==} + hasBin: true + dependencies: + argparse: 2.0.1 + entities: 2.1.0 + linkify-it: 3.0.3 + mdurl: 1.0.1 + uc.micro: 1.0.6 + dev: true + + /marked@4.3.0: + resolution: {integrity: sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==} + engines: {node: '>= 12'} + hasBin: true + dev: true + + /mdurl@1.0.1: + resolution: {integrity: sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==} + dev: true + + /merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + dev: true + + /merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + dev: true + + /micromatch@4.0.5: + resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} + engines: {node: '>=8.6'} + dependencies: + braces: 3.0.2 + picomatch: 2.3.1 + dev: true + + /mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + dev: true + + /mimic-fn@4.0.0: + resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} + engines: {node: '>=12'} + dev: true + + /minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + dependencies: + brace-expansion: 1.1.11 + dev: true + + /minimatch@9.0.4: + resolution: {integrity: sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==} + engines: {node: '>=16 || 14 >=14.17'} + dependencies: + brace-expansion: 2.0.1 + dev: true + + /minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + dev: true + + /minipass@7.0.4: + resolution: {integrity: sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==} + engines: {node: '>=16 || 14 >=14.17'} + dev: true + + /mkdirp2@1.0.5: + resolution: {integrity: sha512-xOE9xbICroUDmG1ye2h4bZ8WBie9EGmACaco8K8cx6RlkJJrxGIqjGqztAI+NMhexXBcdGbSEzI6N3EJPevxZw==} + dev: true + + /mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true + dev: true + + /mlly@1.6.1: + resolution: {integrity: sha512-vLgaHvaeunuOXHSmEbZ9izxPx3USsk8KCQ8iC+aTlp5sKRSoZvwhHh5L9VbKSaVC6sJDqbyohIS76E2VmHIPAA==} + dependencies: + acorn: 8.11.3 + pathe: 1.1.2 + pkg-types: 1.0.3 + ufo: 1.5.3 + dev: true + + /ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + dev: true + + /mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + dev: true + + /nanoid@3.3.7: + resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + dev: true + + /neo-async@2.6.2: + resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + dev: true + + /normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + dev: true + + /npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + dependencies: + path-key: 3.1.1 + dev: true + + /npm-run-path@5.3.0: + resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + path-key: 4.0.0 + dev: true + + /object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + dev: true + + /object-get@2.1.1: + resolution: {integrity: sha512-7n4IpLMzGGcLEMiQKsNR7vCe+N5E9LORFrtNUVy4sO3dj9a3HedZCxEL2T7QuLhcHN1NBuBsMOKaOsAYI9IIvg==} + dev: true + + /object-to-spawn-args@2.0.1: + resolution: {integrity: sha512-6FuKFQ39cOID+BMZ3QaphcC8Y4cw6LXBLyIgPU+OhIYwviJamPAn+4mITapnSBQrejB+NNp+FMskhD8Cq+Ys3w==} + engines: {node: '>=8.0.0'} + dev: true + + /once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + dependencies: + wrappy: 1.0.2 + dev: true + + /onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + dependencies: + mimic-fn: 2.1.0 + dev: true + + /onetime@6.0.0: + resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} + engines: {node: '>=12'} + dependencies: + mimic-fn: 4.0.0 + dev: true + + /p-limit@5.0.0: + resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==} + engines: {node: '>=18'} + dependencies: + yocto-queue: 1.0.0 + dev: true + + /path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + dev: true + + /path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + dev: true + + /path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + dev: true + + /path-scurry@1.10.2: + resolution: {integrity: sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==} + engines: {node: '>=16 || 14 >=14.17'} + dependencies: + lru-cache: 10.2.0 + minipass: 7.0.4 + dev: true + + /path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + dev: true + + /pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + dev: true + + /pathval@1.1.1: + resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} + dev: true + + /picocolors@1.0.0: + resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} + dev: true + + /picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + dev: true + + /pirates@4.0.6: + resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} + engines: {node: '>= 6'} + dev: true + + /pkg-types@1.0.3: + resolution: {integrity: sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==} + dependencies: + jsonc-parser: 3.2.1 + mlly: 1.6.1 + pathe: 1.1.2 + dev: true + + /postcss-load-config@4.0.2: + resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} + engines: {node: '>= 14'} + peerDependencies: + postcss: '>=8.0.9' + ts-node: '>=9.0.0' + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true + dependencies: + lilconfig: 3.1.1 + yaml: 2.4.1 + dev: true + + /postcss@8.4.38: + resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.7 + picocolors: 1.0.0 + source-map-js: 1.2.0 + dev: true + + /pretty-format@29.7.0: + resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/schemas': 29.6.3 + ansi-styles: 5.2.0 + react-is: 18.2.0 + dev: true + + /punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + dev: true + + /queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + dev: true + + /react-is@18.2.0: + resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} + dev: true + + /readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + dependencies: + picomatch: 2.3.1 + dev: true + + /reduce-extract@1.0.0: + resolution: {integrity: sha512-QF8vjWx3wnRSL5uFMyCjDeDc5EBMiryoT9tz94VvgjKfzecHAVnqmXAwQDcr7X4JmLc2cjkjFGCVzhMqDjgR9g==} + engines: {node: '>=0.10.0'} + dependencies: + test-value: 1.1.0 + dev: true + + /reduce-flatten@1.0.1: + resolution: {integrity: sha512-j5WfFJfc9CoXv/WbwVLHq74i/hdTUpy+iNC534LxczMRP67vJeK3V9JOdnL0N1cIRbn9mYhE2yVjvvKXDxvNXQ==} + engines: {node: '>=0.10.0'} + dev: true + + /reduce-flatten@3.0.1: + resolution: {integrity: sha512-bYo+97BmUUOzg09XwfkwALt4PQH1M5L0wzKerBt6WLm3Fhdd43mMS89HiT1B9pJIqko/6lWx3OnV4J9f2Kqp5Q==} + engines: {node: '>=8'} + dev: true + + /reduce-unique@2.0.1: + resolution: {integrity: sha512-x4jH/8L1eyZGR785WY+ePtyMNhycl1N2XOLxhCbzZFaqF4AXjLzqSxa2UHgJ2ZVR/HHyPOvl1L7xRnW8ye5MdA==} + engines: {node: '>=6'} + dev: true + + /reduce-without@1.0.1: + resolution: {integrity: sha512-zQv5y/cf85sxvdrKPlfcRzlDn/OqKFThNimYmsS3flmkioKvkUGn2Qg9cJVoQiEvdxFGLE0MQER/9fZ9sUqdxg==} + engines: {node: '>=0.10.0'} + dependencies: + test-value: 2.1.0 + dev: true + + /regenerator-runtime@0.14.1: + resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} + dev: false + + /requizzle@0.2.4: + resolution: {integrity: sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==} + dependencies: + lodash: 4.17.21 + dev: true + + /resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + dev: true + + /reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + dev: true + + /rollup@4.14.0: + resolution: {integrity: sha512-Qe7w62TyawbDzB4yt32R0+AbIo6m1/sqO7UPzFS8Z/ksL5mrfhA0v4CavfdmFav3D+ub4QeAgsGEe84DoWe/nQ==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + dependencies: + '@types/estree': 1.0.5 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.14.0 + '@rollup/rollup-android-arm64': 4.14.0 + '@rollup/rollup-darwin-arm64': 4.14.0 + '@rollup/rollup-darwin-x64': 4.14.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.14.0 + '@rollup/rollup-linux-arm64-gnu': 4.14.0 + '@rollup/rollup-linux-arm64-musl': 4.14.0 + '@rollup/rollup-linux-powerpc64le-gnu': 4.14.0 + '@rollup/rollup-linux-riscv64-gnu': 4.14.0 + '@rollup/rollup-linux-s390x-gnu': 4.14.0 + '@rollup/rollup-linux-x64-gnu': 4.14.0 + '@rollup/rollup-linux-x64-musl': 4.14.0 + '@rollup/rollup-win32-arm64-msvc': 4.14.0 + '@rollup/rollup-win32-ia32-msvc': 4.14.0 + '@rollup/rollup-win32-x64-msvc': 4.14.0 + fsevents: 2.3.3 + dev: true + + /run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + dependencies: + queue-microtask: 1.2.3 + dev: true + + /shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + dependencies: + shebang-regex: 3.0.0 + dev: true + + /shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + dev: true + + /siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + dev: true + + /signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + dev: true + + /signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + dev: true + + /slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + dev: true + + /sort-array@4.1.5: + resolution: {integrity: sha512-Ya4peoS1fgFN42RN1REk2FgdNOeLIEMKFGJvs7VTP3OklF8+kl2SkpVliZ4tk/PurWsrWRsdNdU+tgyOBkB9sA==} + engines: {node: '>=10'} + dependencies: + array-back: 5.0.0 + typical: 6.0.1 + dev: true + + /source-map-js@1.2.0: + resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} + engines: {node: '>=0.10.0'} + dev: true + + /source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + dev: true + + /source-map@0.8.0-beta.0: + resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} + engines: {node: '>= 8'} + dependencies: + whatwg-url: 7.1.0 + dev: true + + /stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + dev: true + + /std-env@3.7.0: + resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==} + dev: true + + /stream-connect@1.0.2: + resolution: {integrity: sha512-68Kl+79cE0RGKemKkhxTSg8+6AGrqBt+cbZAXevg2iJ6Y3zX4JhA/sZeGzLpxW9cXhmqAcE7KnJCisUmIUfnFQ==} + engines: {node: '>=0.10.0'} + dependencies: + array-back: 1.0.4 + dev: true + + /stream-via@1.0.4: + resolution: {integrity: sha512-DBp0lSvX5G9KGRDTkR/R+a29H+Wk2xItOF+MpZLLNDWbEV9tGPnqLPxHEYjmiz8xGtJHRIqmI+hCjmNzqoA4nQ==} + engines: {node: '>=0.10.0'} + dev: true + + /string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + dev: true + + /string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + dev: true + + /strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + dependencies: + ansi-regex: 5.0.1 + dev: true + + /strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + dependencies: + ansi-regex: 6.0.1 + dev: true + + /strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + dev: true + + /strip-final-newline@3.0.0: + resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} + engines: {node: '>=12'} + dev: true + + /strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + dev: true + + /strip-literal@2.1.0: + resolution: {integrity: sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==} + dependencies: + js-tokens: 9.0.0 + dev: true + + /sucrase@3.35.0: + resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + commander: 4.1.1 + glob: 10.3.12 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.6 + ts-interface-checker: 0.1.13 + dev: true + + /table-layout@0.4.5: + resolution: {integrity: sha512-zTvf0mcggrGeTe/2jJ6ECkJHAQPIYEwDoqsiqBjI24mvRmQbInK5jq33fyypaCBxX08hMkfmdOqj6haT33EqWw==} + engines: {node: '>=4.0.0'} + dependencies: + array-back: 2.0.0 + deep-extend: 0.6.0 + lodash.padend: 4.6.1 + typical: 2.6.1 + wordwrapjs: 3.0.0 + dev: true + + /temp-path@1.0.0: + resolution: {integrity: sha512-TvmyH7kC6ZVTYkqCODjJIbgvu0FKiwQpZ4D1aknE7xpcDf/qEOB8KZEK5ef2pfbVoiBhNWs3yx4y+ESMtNYmlg==} + dev: true + + /test-value@1.1.0: + resolution: {integrity: sha512-wrsbRo7qP+2Je8x8DsK8ovCGyxe3sYfQwOraIY/09A2gFXU9DYKiTF14W4ki/01AEh56kMzAmlj9CaHGDDUBJA==} + engines: {node: '>=0.10.0'} + dependencies: + array-back: 1.0.4 + typical: 2.6.1 + dev: true + + /test-value@2.1.0: + resolution: {integrity: sha512-+1epbAxtKeXttkGFMTX9H42oqzOTufR1ceCF+GYA5aOmvaPq9wd4PUS8329fn2RRLGNeUkgRLnVpycjx8DsO2w==} + engines: {node: '>=0.10.0'} + dependencies: + array-back: 1.0.4 + typical: 2.6.1 + dev: true + + /test-value@3.0.0: + resolution: {integrity: sha512-sVACdAWcZkSU9x7AOmJo5TqE+GyNJknHaHsMrR6ZnhjVlVN9Yx6FjHrsKZ3BjIpPCT68zYesPWkakrNupwfOTQ==} + engines: {node: '>=4.0.0'} + dependencies: + array-back: 2.0.0 + typical: 2.6.1 + dev: true + + /thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + dependencies: + thenify: 3.3.1 + dev: true + + /thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + dependencies: + any-promise: 1.3.0 + dev: true + + /tinybench@2.6.0: + resolution: {integrity: sha512-N8hW3PG/3aOoZAN5V/NSAEDz0ZixDSSt5b/a05iqtpgfLWMSVuCo7w0k2vVvEjdrIoeGqZzweX2WlyioNIHchA==} + dev: true + + /tinypool@0.8.3: + resolution: {integrity: sha512-Ud7uepAklqRH1bvwy22ynrliC7Dljz7Tm8M/0RBUW+YRa4YHhZ6e4PpgE+fu1zr/WqB1kbeuVrdfeuyIBpy4tw==} + engines: {node: '>=14.0.0'} + dev: true + + /tinyspy@2.2.1: + resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==} + engines: {node: '>=14.0.0'} + dev: true + + /to-fast-properties@2.0.0: + resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} + engines: {node: '>=4'} + dev: true + + /to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + dependencies: + is-number: 7.0.0 + dev: true + + /tr46@1.0.1: + resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} + dependencies: + punycode: 2.3.1 + dev: true + + /tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + dev: true + + /ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + dev: true + + /tsup@8.0.2(typescript@5.4.3): + resolution: {integrity: sha512-NY8xtQXdH7hDUAZwcQdY/Vzlw9johQsaqf7iwZ6g1DOUlFYQ5/AtVAjTvihhEyeRlGo4dLRVHtrRaL35M1daqQ==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + '@microsoft/api-extractor': ^7.36.0 + '@swc/core': ^1 + postcss: ^8.4.12 + typescript: '>=4.5.0' + peerDependenciesMeta: + '@microsoft/api-extractor': + optional: true + '@swc/core': + optional: true + postcss: + optional: true + typescript: + optional: true + dependencies: + bundle-require: 4.0.2(esbuild@0.19.12) + cac: 6.7.14 + chokidar: 3.6.0 + debug: 4.3.4 + esbuild: 0.19.12 + execa: 5.1.1 + globby: 11.1.0 + joycon: 3.1.1 + postcss-load-config: 4.0.2 + resolve-from: 5.0.0 + rollup: 4.14.0 + source-map: 0.8.0-beta.0 + sucrase: 3.35.0 + tree-kill: 1.2.2 + typescript: 5.4.3 + transitivePeerDependencies: + - supports-color + - ts-node + dev: true + + /type-detect@4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + engines: {node: '>=4'} + dev: true + + /typescript@5.4.3: + resolution: {integrity: sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==} + engines: {node: '>=14.17'} + hasBin: true + dev: true + + /typical@2.6.1: + resolution: {integrity: sha512-ofhi8kjIje6npGozTip9Fr8iecmYfEbS06i0JnIg+rh51KakryWF4+jX8lLKZVhy6N+ID45WYSFCxPOdTWCzNg==} + dev: true + + /typical@4.0.0: + resolution: {integrity: sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==} + engines: {node: '>=8'} + dev: true + + /typical@6.0.1: + resolution: {integrity: sha512-+g3NEp7fJLe9DPa1TArHm9QAA7YciZmWnfAqEaFrBihQ7epOv9i99rjtgb6Iz0wh3WuQDjsCTDfgRoGnmHN81A==} + engines: {node: '>=10'} + dev: true + + /uc.micro@1.0.6: + resolution: {integrity: sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==} + dev: true + + /ufo@1.5.3: + resolution: {integrity: sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==} + dev: true + + /uglify-js@3.17.4: + resolution: {integrity: sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==} + engines: {node: '>=0.8.0'} + hasBin: true + requiresBuild: true + dev: true + optional: true + + /underscore@1.13.6: + resolution: {integrity: sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==} + dev: true + + /vite-node@1.4.0: + resolution: {integrity: sha512-VZDAseqjrHgNd4Kh8icYHWzTKSCZMhia7GyHfhtzLW33fZlG9SwsB6CEhgyVOWkJfJ2pFLrp/Gj1FSfAiqH9Lw==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + dependencies: + cac: 6.7.14 + debug: 4.3.4 + pathe: 1.1.2 + picocolors: 1.0.0 + vite: 5.2.8 + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + dev: true + + /vite@5.2.8: + resolution: {integrity: sha512-OyZR+c1CE8yeHw5V5t59aXsUPPVTHMDjEZz8MgguLL/Q7NblxhZUlTu9xSPqlsUO/y+X7dlU05jdhvyycD55DA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + dependencies: + esbuild: 0.20.2 + postcss: 8.4.38 + rollup: 4.14.0 + optionalDependencies: + fsevents: 2.3.3 + dev: true + + /vitest@1.4.0: + resolution: {integrity: sha512-gujzn0g7fmwf83/WzrDTnncZt2UiXP41mHuFYFrdwaLRVQ6JYQEiME2IfEjU3vcFL3VKa75XhI3lFgn+hfVsQw==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': 1.4.0 + '@vitest/ui': 1.4.0 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + dependencies: + '@vitest/expect': 1.4.0 + '@vitest/runner': 1.4.0 + '@vitest/snapshot': 1.4.0 + '@vitest/spy': 1.4.0 + '@vitest/utils': 1.4.0 + acorn-walk: 8.3.2 + chai: 4.4.1 + debug: 4.3.4 + execa: 8.0.1 + local-pkg: 0.5.0 + magic-string: 0.30.8 + pathe: 1.1.2 + picocolors: 1.0.0 + std-env: 3.7.0 + strip-literal: 2.1.0 + tinybench: 2.6.0 + tinypool: 0.8.3 + vite: 5.2.8 + vite-node: 1.4.0 + why-is-node-running: 2.2.2 + transitivePeerDependencies: + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + dev: true + + /walk-back@2.0.1: + resolution: {integrity: sha512-Nb6GvBR8UWX1D+Le+xUq0+Q1kFmRBIWVrfLnQAOmcpEzA9oAxwJ9gIr36t9TWYfzvWRvuMtjHiVsJYEkXWaTAQ==} + engines: {node: '>=0.10.0'} + dev: true + + /walk-back@5.1.0: + resolution: {integrity: sha512-Uhxps5yZcVNbLEAnb+xaEEMdgTXl9qAQDzKYejG2AZ7qPwRQ81lozY9ECDbjLPNWm7YsO1IK5rsP1KoQzXAcGA==} + engines: {node: '>=12.17'} + dev: true + + /webidl-conversions@4.0.2: + resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} + dev: true + + /whatwg-url@7.1.0: + resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} + dependencies: + lodash.sortby: 4.7.0 + tr46: 1.0.1 + webidl-conversions: 4.0.2 + dev: true + + /which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + dependencies: + isexe: 2.0.0 + dev: true + + /why-is-node-running@2.2.2: + resolution: {integrity: sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==} + engines: {node: '>=8'} + hasBin: true + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + dev: true + + /wordwrap@1.0.0: + resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + dev: true + + /wordwrapjs@3.0.0: + resolution: {integrity: sha512-mO8XtqyPvykVCsrwj5MlOVWvSnCdT+C+QVbm6blradR7JExAhbkZ7hZ9A+9NUtwzSqrlUo9a67ws0EiILrvRpw==} + engines: {node: '>=4.0.0'} + dependencies: + reduce-flatten: 1.0.1 + typical: 2.6.1 + dev: true + + /wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + dev: true + + /wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + dev: true + + /wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + dev: true + + /xmlcreate@2.0.4: + resolution: {integrity: sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==} + dev: true + + /xregexp@5.1.1: + resolution: {integrity: sha512-fKXeVorD+CzWvFs7VBuKTYIW63YD1e1osxwQ8caZ6o1jg6pDAbABDG54LCIq0j5cy7PjRvGIq6sef9DYPXpncg==} + dependencies: + '@babel/runtime-corejs3': 7.24.4 + dev: false + + /yaml@2.4.1: + resolution: {integrity: sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg==} + engines: {node: '>= 14'} + hasBin: true + dev: true + + /yocto-queue@1.0.0: + resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} + engines: {node: '>=12.20'} + dev: true diff --git a/src/Classifier.js b/src/Classifier.js deleted file mode 100644 index 6c50012..0000000 --- a/src/Classifier.js +++ /dev/null @@ -1,340 +0,0 @@ -import XRegExp from 'xregexp' -import Model from './Model' -import Prediction from './Prediction' -import Vocabulary from './Vocabulary' - -/** - * @param {(Model|Object)} [model] - * @param {int} [model.nGramMin=1] - Minimum n-gram size - * @param {int} [model.nGramMax=1] - Maximum n-gram size - * @param {(Array|Set|false)} [model.vocabulary=[]] - Terms mapped to indexes in the model data entries, set to false to store terms directly in the data entries - * @param {Object} [model.data={}] - Key-value store containing all training data - * @constructor - */ -class Classifier { - constructor(model = {}) { - if (!(model instanceof Model)) { - model = new Model(model) - } - - this._model = model - } - - /** - * Model instance - * - * @type {Model} - */ - get model() { - return this._model - } - - set model(model) { - if (!(model instanceof Model)) { - model = new Model(model) - } - - this._model = model - } - - /** - * Train the current model using an input string (or array of strings) and a corresponding label - * - * @param {(string|string[])} input - String, or an array of strings - * @param {string} label - Corresponding label - * @return {this} - */ - train(input, label) { - if (typeof input !== 'string' && !(input instanceof Array)) { - throw new Error('input must be either a string or Array') - } - - if (typeof label !== 'string') { - throw new Error('label must be a string') - } - - // If input isn't an array, convert to a single item array - if (!(input instanceof Array)) { - input = [input] - } - - input.forEach((string) => { - // Convert the string to a tokenized object - let tokens = this.tokenize(string) - - if (this._model.vocabulary !== false) { - // If we're using a vocabulary, convert the tokens to a vector where all - // indexes reference vocabulary terms - const { vector, vocabulary } = this.vectorize(tokens) - - // Overwrite the tokens object with our new vectorized object - tokens = vector - - // Update the model vocabulary - this._model.vocabulary = vocabulary - } - - // Set up an empty entry for the label if it does not exist - if ( - !Object.prototype.hasOwnProperty.call(this._model.data, label) - ) { - this._model.data[label] = {} - } - - // Add all occurrences to our model entry - Object.keys(tokens).forEach((index) => { - let occurrences = tokens[index] - - if ( - !Object.prototype.hasOwnProperty.call( - this._model.data[label], - index - ) - ) { - this._model.data[label][index] = 0 - } - - this._model.data[label][index] += occurrences - }) - }) - - return this - } - - /** - * Return an array of one or more Prediction instances - * - * @param {string} input - Input string to make a prediction from - * @param {int} [maxMatches=1] Maximum number of predictions to return - * @param {float} [minimumConfidence=0.2] Minimum confidence required to include a prediction - * @return {Array} - */ - predict(input, maxMatches = 1, minimumConfidence = 0.2) { - if (typeof input !== 'string') { - throw new Error('input must be a string') - } - - if (!['number', 'undefined'].includes(typeof maxMatches)) { - throw new Error('maxMatches must be either a number or undefined') - } - - if (!['number', 'undefined'].includes(typeof minimumConfidence)) { - throw new Error( - 'minimumConfidence must be either a number or undefined' - ) - } - - if (minimumConfidence < 0) { - throw new Error('minimumConfidence can not be lower than 0') - } - - if (minimumConfidence > 1) { - throw new Error('minimumConfidence can not be higher than 1') - } - - // Convert the string to a tokenized object - let tokens = this.tokenize(input) - - if (this.vocabulary !== false) { - // If we're using a vocabulary, convert the tokens to a vector where all - // indexes reference vocabulary terms - const { vector } = this.vectorize(tokens) - - // Overwrite the tokens object with our new vectorized object - tokens = vector - } - - const predictions = [] - - Object.keys(this._model.data).forEach((label) => { - let entry = this._model.data[label] - - let confidence = this.cosineSimilarity(tokens, entry) - - if (confidence >= minimumConfidence) { - predictions.push( - new Prediction({ - label, - confidence - }) - ) - } - }) - - /* istanbul ignore next */ - predictions.sort((a, b) => { - if (a.confidence === b.confidence) { - return 0 - } - - return a.confidence > b.confidence ? -1 : 1 - }) - - return predictions.slice(0, Math.min(predictions.length, maxMatches)) - } - - /** - * Split a string into an array of lowercase words, with all non-letter characters removed - * - * @param {string} input - * @return {Array} - */ - splitWords(input) { - if (typeof input !== 'string') { - throw new Error('input must be a string') - } - - // Remove all apostrophes and dashes to keep words intact - input = input.replace(/'|´|’|-/g, '') - - // Lowercase all letters and replace all non-letter characters with a space - input = XRegExp.replace( - input.toLocaleLowerCase(), - XRegExp('\\P{L}+', 'g'), - ' ' - ).trim() - - return input.split(' ') - } - - /** - * Create an object literal of unique tokens (n-grams) as keys, and their - * respective occurrences as values based on an input string, or array of words - * - * @param {(string|string[])} input - * @return {Object} - */ - tokenize(input) { - let words = typeof input === 'string' ? this.splitWords(input) : input - - if (!(words instanceof Array)) { - throw new Error('input must be either a string or Array') - } - - if (this._model.nGramMax < this._model.nGramMin) { - throw new Error( - 'Invalid nGramMin/nGramMax combination in model config' - ) - } - - let tokens = {} - - // Generate a list of n-grams along with their respective occurrences - // based on the models configured min/max values - words.forEach((word, index) => { - let sequence = '' - - words.slice(index).forEach((nextWord) => { - sequence += sequence ? ' ' + nextWord : nextWord - let tokenCount = sequence.split(' ').length - - if ( - tokenCount < this._model.nGramMin || - tokenCount > this._model.nGramMax - ) { - return - } - - if (!Object.prototype.hasOwnProperty.call(tokens, sequence)) { - tokens[sequence] = 0 - } - - ++tokens[sequence] - }) - }) - - return tokens - } - - /** - * Convert a tokenized object into a new object with all keys (terms) - * translated to their index in the returned vocabulary (which is also - * returned along with the object, with any new terms added to the end) - * - * @param {Object} tokens - * @return {Object} - */ - vectorize(tokens) { - if (Object.getPrototypeOf(tokens) !== Object.prototype) { - throw new Error('tokens must be an object literal') - } - - /* istanbul ignore next */ - if (this._model.vocabulary === false) { - throw new Error('Cannot vectorize tokens when vocabulary is false') - } - - const vector = {} - const vocabulary = new Vocabulary(this._model.vocabulary.terms) - - Object.keys(tokens).forEach((token) => { - let vocabularyIndex = vocabulary.indexOf(token) - - if (vocabularyIndex === -1) { - vocabulary.add(token) - - vocabularyIndex = vocabulary.size - 1 - } - - vector[vocabularyIndex] = tokens[token] - }) - - return { - vector, - vocabulary - } - } - - /** - * Return the cosine similarity between two vectors - * - * @param {Object} v1 - * @param {Object} v2 - * @return {float} - */ - cosineSimilarity(v1, v2) { - if (Object.getPrototypeOf(v1) !== Object.prototype) { - throw new Error('v1 must be an object literal') - } - if (Object.getPrototypeOf(v2) !== Object.prototype) { - throw new Error('v2 must be an object literal') - } - - let prod = 0.0 - let v1Norm = 0.0 - - Object.keys(v1).forEach((i) => { - let xi = v1[i] - - if (Object.prototype.hasOwnProperty.call(v2, i)) { - prod += xi * v2[i] - } - - v1Norm += xi * xi - }) - - v1Norm = Math.sqrt(v1Norm) - - if (v1Norm === 0) { - return 0 - } - - let v2Norm = 0.0 - - Object.keys(v2).forEach((i) => { - let xi = v2[i] - - v2Norm += xi * xi - }) - - v2Norm = Math.sqrt(v2Norm) - - if (v2Norm === 0) { - return 0 - } - - return prod / (v1Norm * v2Norm) - } -} - -export default Classifier diff --git a/src/Classifier.ts b/src/Classifier.ts new file mode 100644 index 0000000..f1d9695 --- /dev/null +++ b/src/Classifier.ts @@ -0,0 +1,390 @@ +import XRegExp from 'xregexp'; +import { Model, type ModelConfig } from './Model.js'; +import { Prediction } from './Prediction.js'; +import { Vocabulary } from './Vocabulary.js'; + +export class Classifier { + private _model: Model; + + constructor(model: Model | Partial = {}) { + if (!(model instanceof Model)) { + model = new Model(model); + } + + /** + * @type {Model} + * @private + */ + this._model = model; + } + + /** + * Model instance + * + * @type {Model} + */ + get model() { + return this._model; + } + + set model(model: Model) { + if (!(model instanceof Model)) { + model = new Model(model); + } + + this._model = model; + } + + /** + * Train the current model using an input string (or array of strings) and a corresponding label + * + * @param {(string|string[])} input - String, or an array of strings + * @param {string} label - Corresponding label + * @return {this} + */ + train(input: string | string[], label: string): this { + return this._update(input, label, 'increase'); + } + + /** + * Make the current model put less weight to an input string (or array of strings) and a corresponding label + * + * @param {(string|string[])} input - String, or an array of strings + * @param {string} label - Corresponding label + * @return {this} + */ + demote(input: string | string[], label: string): this { + return this._update(input, label, 'decrease'); + } + + /** + * Make the current model forget an input string (or array of strings) and its corresponding label + * + * @param {(string|string[])} input - String, or an array of strings + * @param {string} label - Corresponding label + * @return {this} + */ + forget(input: string | string[], label: string): this { + return this._update(input, label, 'remove'); + } + + /** + * Train the current model using an input string (or array of strings) and a corresponding label + * + * @param {(string|string[])} input - String, or an array of strings + * @param {string} label - Corresponding label + * @return {this} + */ + private _update( + input: string | string[], + label: string, + type: 'increase' | 'decrease' | 'remove', + ): this { + if (typeof input !== 'string' && !Array.isArray(input)) { + throw new Error('input must be either a string or Array'); + } + + if (typeof label !== 'string') { + throw new Error('label must be a string'); + } + + // If input isn't an array, convert to a single item array + if (!Array.isArray(input)) { + input = [input]; + } + + for (const string of input) { + // Convert the string to a tokenized object + let tokens = this.tokenize(string); + + if (this._model.vocabulary !== false) { + // If we're using a vocabulary, convert the tokens to a vector where all + // indexes reference vocabulary terms + const { vector, vocabulary } = this.vectorize(tokens); + + // Overwrite the tokens object with our new vectorized object + tokens = vector; + + // Update the model vocabulary + this._model.vocabulary = vocabulary; + } + + // Set up an empty entry for the label if it does not exist + if (!Object.prototype.hasOwnProperty.call(this._model.data, label)) { + this._model.data[label] = {}; + } + + // Add all occurrences to our model entry + for (const index of Object.keys(tokens)) { + const occurrences = tokens[index]; + + if ( + type === 'remove' || + !Object.prototype.hasOwnProperty.call(this._model.data[label], index) + ) { + this._model.data[label][index] = 0; + } + + if (type !== 'remove') { + this._model.data[label][index] = Math.max( + this._model.data[label][index] + + (type === 'increase' ? occurrences : -occurrences), + 0, + ); + } + } + } + + return this; + } + + /** + * Return an array of one or more Prediction instances + * + * @param {string} input + * @return {Array} + */ + predict( + input: string, + maxMatches = 1, + minimumConfidence = 0.2, + ): Array { + if (typeof input !== 'string') { + throw new Error('input must be a string'); + } + + if (!['number', 'undefined'].includes(typeof maxMatches)) { + throw new Error('maxMatches must be either a number or undefined'); + } + + if (!['number', 'undefined'].includes(typeof minimumConfidence)) { + throw new Error('minimumConfidence must be either a number or undefined'); + } + + if (minimumConfidence < 0) { + throw new Error('minimumConfidence can not be lower than 0'); + } + + if (minimumConfidence > 1) { + throw new Error('minimumConfidence can not be higher than 1'); + } + + // Convert the string to a tokenized object + let tokens = this.tokenize(input); + + if (this._model.vocabulary !== false) { + // If we're using a vocabulary, convert the tokens to a vector where all + // indexes reference vocabulary terms + const { vector } = this.vectorize(tokens); + + // Overwrite the tokens object with our new vectorized object + tokens = vector; + } + + /** + * @type {Prediction[]} + */ + const predictions: Prediction[] = []; + + for (const label of Object.keys(this._model.data)) { + const entry = this._model.data[label]; + const confidence = this.cosineSimilarity(tokens, entry); + + if (confidence >= minimumConfidence) { + predictions.push( + new Prediction({ + label, + confidence, + }), + ); + } + } + + /* istanbul ignore next */ + predictions.sort((a, b) => { + if (a.confidence === b.confidence) { + return 0; + } + + return a.confidence > b.confidence ? -1 : 1; + }); + + return predictions.slice(0, Math.min(predictions.length, maxMatches)); + } + + /** + * Split a string into an array of lowercase words, with all non-letter characters removed + * + * @param {string} input + * @return {string[]} + */ + splitWords(input: string): string[] { + if (typeof input !== 'string') { + throw new Error('input must be a string'); + } + + // Remove all apostrophes and dashes to keep words intact + input = input.replace(/'|´|’|-/g, ''); + + // Lowercase all letters and replace all non-letter characters with a space + input = XRegExp.replace( + input.toLocaleLowerCase(), + XRegExp('\\P{L}+', 'g'), + ' ', + ).trim(); + + return input.split(' '); + } + + /** + * Create an object literal of unique tokens (n-grams) as keys, and their + * respective occurrences as values based on an input string, or array of words + * + * @param {(string|string[])} input + * @return {Record} + */ + tokenize(input: string | string[]): Record { + const words = typeof input === 'string' ? this.splitWords(input) : input; + + if (!Array.isArray(words)) { + throw new Error('input must be either a string or Array'); + } + + if (this._model.nGramMax < this._model.nGramMin) { + throw new Error('Invalid nGramMin/nGramMax combination in model config'); + } + + /** + * @type {Record} + */ + const tokens: Record = {}; + + // Generate a list of n-grams along with their respective occurrences + // based on the models configured min/max values + words.forEach((_, index) => { + let sequence = ''; + let tokenCount = 0; + let nextWord: string; + + // Create n-gram(s) of between nGramMin and nGramMax words from segment starting at (index) + // Increment the occurrence counter (tokens[sequence]) for each n-gram created + // Stop looping once we have nGramMax words (or reach the end of the segment) + const segment = words.slice(index); + while (tokenCount < this._model.nGramMax && tokenCount < segment.length) { + nextWord = segment[tokenCount]; + sequence += sequence ? ` ${nextWord}` : nextWord; + tokenCount++; + if ( + tokenCount >= this._model.nGramMin && + tokenCount <= this._model.nGramMax + ) { + if (typeof tokens[sequence] === 'undefined') { + tokens[sequence] = 0; + } + + ++tokens[sequence]; + } + } + }); + + return tokens; + } + + /** + * Convert a tokenized object into a new object with all keys (terms) + * translated to their index in the returned vocabulary (which is also + * returned along with the object, with any new terms added to the end) + * + * @param {Record} tokens + * @return {{vector: Record, vocabulary: Vocabulary}} + */ + vectorize(tokens: Record): { + vector: Record; + vocabulary: Vocabulary; + } { + if (Object.getPrototypeOf(tokens) !== Object.prototype) { + throw new Error('tokens must be an object literal'); + } + + /* istanbul ignore next */ + if (this._model.vocabulary === false) { + throw new Error('Cannot vectorize tokens when vocabulary is false'); + } + + /** + * @type {Record} + */ + const vector: Record = {}; + const vocabulary = new Vocabulary(this._model.vocabulary.terms); + + for (const token of Object.keys(tokens)) { + let vocabularyIndex = vocabulary.indexOf(token); + + if (vocabularyIndex === -1) { + vocabulary.add(token); + + vocabularyIndex = vocabulary.size - 1; + } + + vector[vocabularyIndex] = tokens[token]; + } + + return { + vector, + vocabulary, + }; + } + + /** + * Return the cosine similarity between two vectors + * + * @param {Record} v1 + * @param {Record} v2 + * @return {number} + */ + cosineSimilarity( + v1: Record, + v2: Record, + ): number { + if (Object.getPrototypeOf(v1) !== Object.prototype) { + throw new Error('v1 must be an object literal'); + } + if (Object.getPrototypeOf(v2) !== Object.prototype) { + throw new Error('v2 must be an object literal'); + } + + let prod = 0.0; + let v1Norm = 0.0; + + for (const i of Object.keys(v1)) { + const xi = v1[i]; + + if (Object.prototype.hasOwnProperty.call(v2, i)) { + prod += xi * v2[i]; + } + + v1Norm += xi * xi; + } + + v1Norm = Math.sqrt(v1Norm); + + if (v1Norm === 0) { + return 0; + } + + let v2Norm = 0.0; + + for (const i of Object.keys(v2)) { + const xi = v2[i]; + v2Norm += xi * xi; + } + + v2Norm = Math.sqrt(v2Norm); + + if (v2Norm === 0) { + return 0; + } + + return prod / (v1Norm * v2Norm); + } +} diff --git a/src/Model.js b/src/Model.js deleted file mode 100644 index a27dbb6..0000000 --- a/src/Model.js +++ /dev/null @@ -1,147 +0,0 @@ -import Vocabulary from './Vocabulary' - -/** - * @param {Object} [config] - * @param {int} [config.nGramMin=1] - Minimum n-gram size - * @param {int} [config.nGramMax=1] - Maximum n-gram size - * @param {(Array|Set|false)} [config.vocabulary=[]] - Terms mapped to indexes in the model data entries, set to false to store terms directly in the data entries - * @param {Object} [config.data={}] - Key-value store containing all training data - * @constructor - */ -class Model { - constructor(config = {}) { - if (Object.getPrototypeOf(config) !== Object.prototype) { - throw new Error('config must be an object literal') - } - - config = { - nGramMin: 1, - nGramMax: 1, - vocabulary: [], - data: {}, - ...config - } - - if (config.nGramMin !== parseInt(config.nGramMin, 10)) { - throw new Error('Config value nGramMin must be an integer') - } - - if (config.nGramMax !== parseInt(config.nGramMax, 10)) { - throw new Error('Config value nGramMax must be an integer') - } - - if (config.nGramMin < 1) { - throw new Error('Config value nGramMin must be at least 1') - } - - if (config.nGramMax < 1) { - throw new Error('Config value nGramMax must be at least 1') - } - - if (config.nGramMax < config.nGramMin) { - throw new Error('Invalid nGramMin/nGramMax combination in config') - } - - if ( - config.vocabulary !== false && - !(config.vocabulary instanceof Vocabulary) - ) { - config.vocabulary = new Vocabulary(config.vocabulary) - } - - if (Object.getPrototypeOf(config.data) !== Object.prototype) { - throw new Error('Config value data must be an object literal') - } - - this._nGramMin = config.nGramMin - this._nGramMax = config.nGramMax - this._vocabulary = config.vocabulary - this._data = { ...config.data } - } - - /** - * Minimum n-gram size - * - * @type {int} - */ - get nGramMin() { - return this._nGramMin - } - - set nGramMin(size) { - if (size !== parseInt(size, 10)) { - throw new Error('nGramMin must be an integer') - } - - this._nGramMin = size - } - - /** - * Maximum n-gram size - * - * @type {int} - */ - get nGramMax() { - return this._nGramMax - } - - set nGramMax(size) { - if (size !== parseInt(size, 10)) { - throw new Error('nGramMax must be an integer') - } - - this._nGramMax = size - } - - /** - * Vocabulary instance - * - * @type {(Vocabulary|false)} - */ - get vocabulary() { - return this._vocabulary - } - - set vocabulary(vocabulary) { - if (vocabulary !== false && !(vocabulary instanceof Vocabulary)) { - vocabulary = new Vocabulary(vocabulary) - } - - this._vocabulary = vocabulary - } - - /** - * Model data - * - * @type {Object} - */ - get data() { - return this._data - } - - set data(data) { - if (!(data instanceof Object) || data.constructor !== Object) { - throw new Error('data must be an object literal') - } - - this._data = { ...data } - } - - /** - * Return the model in its current state an an object literal, including the - * configured n-gram min/max values, the vocabulary as an array (if any, - * otherwise false), and an object literal with all the training data - * - * @return {Object} - */ - serialize() { - return { - nGramMin: this._nGramMin, - nGramMax: this._nGramMax, - vocabulary: Array.from(this._vocabulary.terms), - data: this._data - } - } -} - -export default Model diff --git a/src/Model.ts b/src/Model.ts new file mode 100644 index 0000000..216ba07 --- /dev/null +++ b/src/Model.ts @@ -0,0 +1,177 @@ +import { Vocabulary } from './Vocabulary.js'; + +export type ModelData = Record>; + +export type ModelConfig = { + nGramMin: number; + nGramMax: number; + vocabulary: string[] | Set | false; + data: ModelData; +}; + +export class Model { + private _nGramMin: number; + private _nGramMax: number; + private _data: { [x: string]: Record }; + private _vocabulary: Vocabulary | false; + + /** + * @param {Partial} config + */ + constructor(config: Partial = {}) { + if (Object.getPrototypeOf(config) !== Object.prototype) { + throw new Error('config must be an object literal'); + } + + config = { + nGramMin: 1, + nGramMax: 1, + vocabulary: [], + data: {}, + ...config, + }; + + if (config.nGramMin !== Number.parseInt(String(config.nGramMin), 10)) { + throw new Error('Config value nGramMin must be an integer'); + } + + if (config.nGramMax !== Number.parseInt(String(config.nGramMax), 10)) { + throw new Error('Config value nGramMax must be an integer'); + } + + if (config.nGramMin < 1) { + throw new Error('Config value nGramMin must be at least 1'); + } + + if (config.nGramMax < 1) { + throw new Error('Config value nGramMax must be at least 1'); + } + + if (config.nGramMax < config.nGramMin) { + throw new Error('Invalid nGramMin/nGramMax combination in config'); + } + + if ( + config.vocabulary !== false && + !(config.vocabulary instanceof Vocabulary) + ) { + /** + * @type {Vocabulary|false} + * @private + */ + this._vocabulary = new Vocabulary(config.vocabulary); + } else { + /** + * @type {Vocabulary|false} + * @private + */ + this._vocabulary = config.vocabulary; + } + + if (Object.getPrototypeOf(config.data) !== Object.prototype) { + throw new Error('Config value data must be an object literal'); + } + + /** + * @type {number} + * @private + */ + this._nGramMin = config.nGramMin; + + /** + * @type {number} + * @private + */ + this._nGramMax = config.nGramMax; + + /** + * @type {ModelData} + * @private + */ + this._data = { ...config.data }; + } + + /** + * Minimum n-gram size + * + * @type {number} + */ + get nGramMin() { + return this._nGramMin; + } + + set nGramMin(size) { + if (size !== Number.parseInt(String(size), 10)) { + throw new Error('nGramMin must be an integer'); + } + + this._nGramMin = size; + } + + /** + * Maximum n-gram size + * + * @type {number} + */ + get nGramMax() { + return this._nGramMax; + } + + set nGramMax(size) { + if (size !== Number.parseInt(String(size), 10)) { + throw new Error('nGramMax must be an integer'); + } + + this._nGramMax = size; + } + + /** + * Vocabulary instance + * + * @type {(Vocabulary|false)} + */ + get vocabulary() { + return this._vocabulary; + } + + set vocabulary(v: Vocabulary | false) { + if (v !== false && !(v instanceof Vocabulary)) { + v = new Vocabulary(v); + } + + this._vocabulary = v; + } + + /** + * Model data + * + * @type {ModelData} + */ + get data() { + return this._data; + } + + set data(data) { + if (!(data instanceof Object) || data.constructor !== Object) { + throw new Error('data must be an object literal'); + } + + this._data = { ...data }; + } + + /** + * Return the model in its current state an an object literal, including the + * configured n-gram min/max values, the vocabulary as an array (if any, + * otherwise false), and an object literal with all the training data + * + * @return {ModelConfig} + */ + serialize() { + return { + nGramMin: this._nGramMin, + nGramMax: this._nGramMax, + vocabulary: this._vocabulary ? Array.from(this._vocabulary.terms) : false, + data: this._data, + }; + } +} diff --git a/src/Prediction.js b/src/Prediction.js deleted file mode 100644 index 6bd4db5..0000000 --- a/src/Prediction.js +++ /dev/null @@ -1,57 +0,0 @@ -/** - * @param {Object} prediction - * @constructor - * @hideconstructor - */ -class Prediction { - constructor(prediction = {}) { - if (Object.getPrototypeOf(prediction) !== Object.prototype) { - throw new Error('prediction must be an object literal') - } - - prediction = { - label: '', - confidence: 0, - ...prediction - } - - this._label = prediction.label - this._confidence = prediction.confidence - } - - /** - * Label of the prediction - * - * @type {string} - */ - get label() { - return this._label - } - - set label(label) { - if (typeof label !== 'string') { - throw new Error('label must be a string') - } - - this._label = label - } - - /** - * Confidence of the prediction - * - * @type {number} - */ - get confidence() { - return this._confidence - } - - set confidence(confidence) { - if (typeof confidence !== 'number') { - throw new Error('confidence must be a number') - } - - this._confidence = confidence - } -} - -export default Prediction diff --git a/src/Prediction.ts b/src/Prediction.ts new file mode 100644 index 0000000..5b9e48e --- /dev/null +++ b/src/Prediction.ts @@ -0,0 +1,59 @@ +export class Prediction { + private _label: string; + private _confidence: number; + + /** + * @param {{ label?: string, confidence?: number }} prediction + */ + constructor(prediction: { label?: string; confidence?: number } = {}) { + if (Object.getPrototypeOf(prediction) !== Object.prototype) { + throw new Error('prediction must be an object literal'); + } + + /** + * @type {string} + * @private + */ + this._label = prediction.label ?? ''; + + /** + * @type {number} + * @private + */ + this._confidence = prediction.confidence ?? 0; + } + + /** + * Label of the prediction + * + * @type {string} + */ + get label() { + return this._label; + } + + set label(label) { + if (typeof label !== 'string') { + throw new Error('label must be a string'); + } + + this._label = label; + } + + /** + * Confidence of the prediction + * + * @type {number} + */ + get confidence() { + return this._confidence; + } + + set confidence(confidence) { + if (typeof confidence !== 'number') { + throw new Error('confidence must be a number'); + } + + this._confidence = confidence; + } +} diff --git a/src/Vocabulary.js b/src/Vocabulary.js deleted file mode 100644 index d322f7f..0000000 --- a/src/Vocabulary.js +++ /dev/null @@ -1,121 +0,0 @@ -/** - * @param {Array|Set} terms - * @constructor - */ -class Vocabulary { - constructor(terms = []) { - if (!(terms instanceof Array) && !(terms instanceof Set)) { - throw new Error('terms must be either an Array or a Set') - } - - this._terms = new Set(terms) - } - - /** - * Vocabulary size - * - * @type {number} - */ - get size() { - return this._terms.size - } - - /** - * Vocabulary terms - * - * @type {(Array|Set)} - */ - get terms() { - return this._terms - } - - set terms(terms) { - if (!(terms instanceof Array) && !(terms instanceof Set)) { - throw new Error('terms must be either an Array or a Set') - } - - this._terms = new Set(terms) - } - - /** - * Add one or more terms to the vocabulary - * - * @param {(string|Array|Set)} terms - * @return {this} - */ - add(terms) { - if ( - typeof terms !== 'string' && - !(terms instanceof Array) && - !(terms instanceof Set) - ) { - throw new Error('terms must be either a string, Array or Set') - } - - if (typeof terms === 'string') { - terms = [terms] - } else if (terms instanceof Set) { - terms = Array.from(terms) - } - - terms.forEach((term) => { - this._terms.add(term) - }) - - return this - } - - /** - * Remove one or more terms from the vocabulary - * - * @param {(string|Array|Set)} terms - * @return {this} - */ - remove(terms) { - if ( - typeof terms !== 'string' && - !(terms instanceof Array) && - !(terms instanceof Set) - ) { - throw new Error('terms must be either a string, Array or Set') - } - - if (typeof terms === 'string') { - terms = [terms] - } else if (terms instanceof Set) { - terms = Array.from(terms) - } - - terms.forEach((term) => { - this._terms.delete(term) - }) - - return this - } - - /** - * Return whether the vocabulary contains a certain term - * - * @param {string} term - * @return {bool} - */ - has(term) { - return this._terms.has(term) - } - - /** - * Return the index of a term in the vocabulary (returns -1 if not found) - * - * @param {string} term - * @return {number} - */ - indexOf(term) { - if (!this._terms.has(term)) { - return -1 - } - - return Array.from(this._terms).indexOf(term) - } -} - -export default Vocabulary diff --git a/src/Vocabulary.ts b/src/Vocabulary.ts new file mode 100644 index 0000000..7a9c487 --- /dev/null +++ b/src/Vocabulary.ts @@ -0,0 +1,124 @@ +export class Vocabulary { + private _terms: Set; + + /** + * @param {Array|Set} terms + */ + constructor(terms: Array | Set = []) { + if (!Array.isArray(terms) && !(terms instanceof Set)) { + throw new Error('terms must be either an Array or a Set'); + } + + /** + * @type {Set} + * @private + */ + this._terms = new Set(terms); + } + + /** + * Vocabulary size + * + * @type {number} + */ + get size() { + return this._terms.size; + } + + /** + * Vocabulary terms + * + * @type {Set} + */ + get terms() { + return this._terms; + } + + set terms(terms: Set | string[]) { + if (!Array.isArray(terms) && !(terms instanceof Set)) { + throw new Error('terms must be either an Array or a Set'); + } + + this._terms = new Set(terms); + } + + /** + * Add one or more terms to the vocabulary + * + * @param {(string|Array|Set)} terms + * @return {this} + */ + add(terms: string | Array | Set): this { + if ( + typeof terms !== 'string' && + !Array.isArray(terms) && + !(terms instanceof Set) + ) { + throw new Error('terms must be either a string, Array or Set'); + } + + if (typeof terms === 'string') { + terms = [terms]; + } else if (terms instanceof Set) { + terms = Array.from(terms); + } + + for (const term of terms) { + this._terms.add(term); + } + + return this; + } + + /** + * Remove one or more terms from the vocabulary + * + * @param {(string|Array|Set)} terms + * @return {this} + */ + remove(terms: string | Array | Set): this { + if ( + typeof terms !== 'string' && + !Array.isArray(terms) && + !(terms instanceof Set) + ) { + throw new Error('terms must be either a string, Array or Set'); + } + + if (typeof terms === 'string') { + terms = [terms]; + } else if (terms instanceof Set) { + terms = Array.from(terms); + } + + for (const term of terms) { + this._terms.delete(term); + } + + return this; + } + + /** + * Return whether the vocabulary contains a certain term + * + * @param {string} term + * @return {boolean} + */ + has(term: string): boolean { + return this._terms.has(term); + } + + /** + * Return the index of a term in the vocabulary (returns -1 if not found) + * + * @param {string} term + * @return {number} + */ + indexOf(term: string): number { + if (!this._terms.has(term)) { + return -1; + } + + return Array.from(this._terms).indexOf(term); + } +} diff --git a/src/index.js b/src/index.js deleted file mode 100644 index c995579..0000000 --- a/src/index.js +++ /dev/null @@ -1,8 +0,0 @@ -import Classifier from './Classifier' - -export { default as Model } from './Model' -export { default as Vocabulary } from './Vocabulary' -export { default as Prediction } from './Prediction' -export { Classifier as Classifier } - -export default Classifier diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..18bc2c8 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,4 @@ +export { Classifier } from './Classifier.js'; +export { Model, type ModelConfig, type ModelData } from './Model.js'; +export { Vocabulary } from './Vocabulary.js'; +export { Prediction } from './Prediction.js'; diff --git a/test/Classifier.test.js b/test/Classifier.test.ts similarity index 50% rename from test/Classifier.test.js rename to test/Classifier.test.ts index bb274b7..68dc2e4 100644 --- a/test/Classifier.test.js +++ b/test/Classifier.test.ts @@ -1,427 +1,448 @@ -import Classifier from '../src/Classifier' -import Model from '../src/Model' +import { assert, describe, expect, test } from 'vitest'; + +import { Classifier } from '../src/Classifier.js'; +import { Model } from '../src/Model.js'; describe('Classifier', () => { describe('constructor', () => { test('should set the model when passed a model instance', () => { const classifier = new Classifier( new Model({ - nGramMax: 4 - }) - ) + nGramMax: 4, + }), + ); - expect(classifier.model.nGramMax).toStrictEqual(4) - }) + expect(classifier.model.nGramMax).toStrictEqual(4); + }); test('should set the model when passed an object literal', () => { const classifier = new Classifier({ - nGramMax: 5 - }) + nGramMax: 5, + }); - expect(classifier.model.nGramMax).toStrictEqual(5) - }) - }) + expect(classifier.model.nGramMax).toStrictEqual(5); + }); + }); describe('model', () => { test('should return a model instance', () => { - let classifier = new Classifier() + const classifier = new Classifier(); - expect(classifier.model).toBeInstanceOf(Model) - }) + expect(classifier.model).toBeInstanceOf(Model); + }); test('should set the current model when passed a model instance', () => { - let classifier = new Classifier() + const classifier = new Classifier(); classifier.model = new Model({ - nGramMax: 3 - }) + nGramMax: 3, + }); - expect(classifier.model.nGramMax).toStrictEqual(3) - }) + expect(classifier.model.nGramMax).toStrictEqual(3); + }); test('should set the current model to a new model instance when passed an object literal', () => { - let classifier = new Classifier() + const classifier = new Classifier(); - classifier.model = {} + // @ts-expect-error setter type issue + classifier.model = {}; - expect(classifier.model).toBeInstanceOf(Model) - }) - }) + expect(classifier.model).toBeInstanceOf(Model); + }); + }); describe('splitWords', () => { test('should throw an error if input is not a string', () => { - const classifier = new Classifier() + const classifier = new Classifier(); - expect(() => classifier.splitWords(1)).toThrow(Error) - }) + // @ts-expect-error Invalid type check + expect(() => classifier.splitWords(1)).toThrow(Error); + }); test('should split a string into an array of words', () => { - const classifier = new Classifier() + const classifier = new Classifier(); expect(classifier.splitWords('Hello world!')).toStrictEqual([ 'hello', - 'world' - ]) - }) - }) + 'world', + ]); + }); + }); describe('tokenize', () => { test('should throw an error if input is neither a string or array', () => { - const classifier = new Classifier() + const classifier = new Classifier(); - expect(() => classifier.tokenize({})).toThrow(Error) - }) + // @ts-expect-error Invalid type check + expect(() => classifier.tokenize({})).toThrow(Error); + }); test('should throw an error if nGramMax is less than nGramMin in model config', () => { - const classifier = new Classifier() + const classifier = new Classifier(); - classifier.model.nGramMin = 2 + classifier.model.nGramMin = 2; - expect(() => classifier.tokenize('Hello world!')).toThrow(Error) - }) + expect(() => classifier.tokenize('Hello world!')).toThrow(Error); + }); test('should return an object literal of tokens and their occurrences from a string', () => { - const classifier = new Classifier() + const classifier = new Classifier(); expect(classifier.tokenize('Hello world!')).toStrictEqual({ hello: 1, - world: 1 - }) - }) + world: 1, + }); + }); test('should return an object literal of tokens and their occurrences from a string', () => { - const classifier = new Classifier() + const classifier = new Classifier(); expect(classifier.tokenize('Hello world!')).toStrictEqual({ hello: 1, - world: 1 - }) - }) + world: 1, + }); + }); test('should return an object literal of tokens and their occurrences from a array', () => { - const classifier = new Classifier() + const classifier = new Classifier(); expect(classifier.tokenize(['hello', 'world'])).toStrictEqual({ hello: 1, - world: 1 - }) - }) + world: 1, + }); + }); test('should return an object literal of bigrams when nGramMin/nGramMax is 2', () => { const classifier = new Classifier({ nGramMin: 2, - nGramMax: 2 - }) + nGramMax: 2, + }); expect(classifier.tokenize('Hello world!')).toStrictEqual({ - 'hello world': 1 - }) - }) + 'hello world': 1, + }); + }); test('should return an object literal of unigrams and bigrams when nGramMin/nGramMax is 1/2', () => { const classifier = new Classifier({ nGramMin: 1, - nGramMax: 2 - }) + nGramMax: 2, + }); expect(classifier.tokenize('Hello world!')).toStrictEqual({ hello: 1, 'hello world': 1, - world: 1 - }) - }) + world: 1, + }); + }); test('should increment the occurrence of the duplicate tokens', () => { - const classifier = new Classifier() + const classifier = new Classifier(); expect(classifier.tokenize('Hello hello!')).toStrictEqual({ - hello: 2 - }) - }) - }) + hello: 2, + }); + }); + + test('should create a unigrams for the space character from an array of characters including a space', () => { + const classifier = new Classifier(); + + expect(classifier.tokenize([' ', 'a', 'b'])).toEqual({ + ' ': 1, + a: 1, + b: 1, + }); + }); + }); describe('vectorize', () => { test('should throw an error if input is not an object literal', () => { - const classifier = new Classifier() + const classifier = new Classifier(); - expect(() => classifier.vectorize([])).toThrow(Error) - }) + // @ts-expect-error Error checking + expect(() => classifier.vectorize([])).toThrow(Error); + }); test('should throw an error if vocabulary config option is set to false', () => { const classifier = new Classifier({ - vocabulary: false - }) + vocabulary: false, + }); - expect(() => classifier.vectorize({ hello: 1 })).toThrow(Error) - }) + expect(() => classifier.vectorize({ hello: 1 })).toThrow(Error); + }); test('should convert key to its corresponding vocabulary term index', () => { - const classifier = new Classifier() - const tokens = classifier.tokenize('Hello') + const classifier = new Classifier(); + const tokens = classifier.tokenize('Hello'); - const { vector } = classifier.vectorize(tokens) + const { vector } = classifier.vectorize(tokens); - expect(vector).toStrictEqual({ 0: 1 }) - }) + expect(vector).toStrictEqual({ 0: 1 }); + }); test('should use existing term index when token is already in vocabulary', () => { const classifier = new Classifier({ - vocabulary: ['hello', 'world'] - }) + vocabulary: ['hello', 'world'], + }); - const tokens = classifier.tokenize('world') + const tokens = classifier.tokenize('world'); - const { vector } = classifier.vectorize(tokens) + const { vector } = classifier.vectorize(tokens); - expect(vector).toStrictEqual({ 1: 1 }) - }) + expect(vector).toStrictEqual({ 1: 1 }); + }); test('should return an updated copy of the vocabulary', () => { - const classifier = new Classifier() + const classifier = new Classifier(); - const tokens = classifier.tokenize('Hello world') + const tokens = classifier.tokenize('Hello world'); - const { vocabulary } = classifier.vectorize(tokens) + const { vocabulary } = classifier.vectorize(tokens); - const terms = vocabulary.terms + const terms = vocabulary.terms; - expect(Array.from(terms)).toStrictEqual(['hello', 'world']) - }) - }) + expect(Array.from(terms)).toStrictEqual(['hello', 'world']); + }); + }); describe('train', () => { test('should throw an error if input is not a string or array', () => { - const classifier = new Classifier() + const classifier = new Classifier(); - expect(() => classifier.train({}, 'test')).toThrow(Error) - }) + // @ts-expect-error Invalid type check + expect(() => classifier.train({}, 'test')).toThrow(Error); + }); test('should throw an error if label is not a string', () => { - const classifier = new Classifier() + const classifier = new Classifier(); - expect(() => classifier.train('test', [])).toThrow(Error) - }) + // @ts-expect-error Invalid type check + expect(() => classifier.train('test', [])).toThrow(Error); + }); test('should add tokens to the vocabulary (if not configured to false)', () => { - const classifier = new Classifier() + const classifier = new Classifier(); - classifier.train('hello world', 'test') + classifier.train('hello world', 'test'); - const vocabulary = classifier.model.vocabulary + const vocabulary = classifier.model.vocabulary; - expect(vocabulary.size).toStrictEqual(2) - }) + assert(vocabulary); + expect(vocabulary.size).toStrictEqual(2); + }); test('should add tokens (and their occurrences) to the model from a string', () => { - const classifier = new Classifier() + const classifier = new Classifier(); - classifier.train('hello world', 'test') + classifier.train('hello world', 'test'); - const model = classifier.model + const model = classifier.model; expect(model.data).toStrictEqual({ - test: { 0: 1, 1: 1 } - }) - }) + test: { 0: 1, 1: 1 }, + }); + }); test('should add tokens (and their occurrences) to the model from an array of strings', () => { - const classifier = new Classifier() + const classifier = new Classifier(); - classifier.train(['hello world', 'foo', 'bar'], 'test') + classifier.train(['hello world', 'foo', 'bar'], 'test'); - const model = classifier.model + const model = classifier.model; expect(model.data).toStrictEqual({ - test: { 0: 1, 1: 1, 2: 1, 3: 1 } - }) - }) + test: { 0: 1, 1: 1, 2: 1, 3: 1 }, + }); + }); test('should increment the occurrence of an existing vocabulary term', () => { - const classifier = new Classifier() + const classifier = new Classifier(); - classifier.train(['hello world', 'foo', 'hello'], 'test') + classifier.train(['hello world', 'foo', 'hello'], 'test'); - const model = classifier.model + const model = classifier.model; expect(model.data).toStrictEqual({ - test: { 0: 2, 1: 1, 2: 1 } - }) - }) + test: { 0: 2, 1: 1, 2: 1 }, + }); + }); test('should return classifier instance', () => { - const classifier = new Classifier() + const classifier = new Classifier(); - expect(classifier.train('hello world', 'test')).toStrictEqual( - classifier - ) - }) - }) + expect(classifier.train('hello world', 'test')).toStrictEqual(classifier); + }); + }); describe('cosineSimilarity', () => { test('should throw an error if v1 is not an object literal', () => { - const classifier = new Classifier() + const classifier = new Classifier(); - expect(() => classifier.cosineSimilarity(false, {})).toThrow(Error) - }) + // @ts-expect-error Invalid type check + expect(() => classifier.cosineSimilarity(false, {})).toThrow(Error); + }); test('should throw an error if v2 is not an object literal', () => { - const classifier = new Classifier() + const classifier = new Classifier(); - expect(() => classifier.cosineSimilarity({}, false)).toThrow(Error) - }) + // @ts-expect-error Invalid type check + expect(() => classifier.cosineSimilarity({}, false)).toThrow(Error); + }); test('should return 1 on identical object literals', () => { - const classifier = new Classifier() + const classifier = new Classifier(); expect( classifier.cosineSimilarity( { - 0: 1 + 0: 1, }, { - 0: 1 - } - ) - ).toStrictEqual(1) - }) + 0: 1, + }, + ), + ).toStrictEqual(1); + }); test('should return 0 on object literals with no similarity', () => { - const classifier = new Classifier() + const classifier = new Classifier(); expect( classifier.cosineSimilarity( { - 0: 1 + 0: 1, }, { - 1: 1 - } - ) - ).toStrictEqual(0) - }) + 1: 1, + }, + ), + ).toStrictEqual(0); + }); test('should return > 0 on similar object literals', () => { - const classifier = new Classifier() + const classifier = new Classifier(); expect( classifier.cosineSimilarity( { 0: 1, - 1: 1 + 1: 1, }, { 0: 1, - 2: 1 - } - ) - ).toBeGreaterThan(0) - }) + 2: 1, + }, + ), + ).toBeGreaterThan(0); + }); test('should return 0 when sum of v1 is 0', () => { - const classifier = new Classifier() + const classifier = new Classifier(); expect( classifier.cosineSimilarity( { - 0: 0 + 0: 0, }, { - 0: 1 - } - ) - ).toStrictEqual(0) - }) + 0: 1, + }, + ), + ).toStrictEqual(0); + }); test('should return 0 when sum of v2 is 0', () => { - const classifier = new Classifier() + const classifier = new Classifier(); expect( classifier.cosineSimilarity( { - 0: 1 + 0: 1, }, { - 0: 0 - } - ) - ).toStrictEqual(0) - }) - }) + 0: 0, + }, + ), + ).toStrictEqual(0); + }); + }); describe('predict', () => { test('should throw an error if input is not a string', () => { - const classifier = new Classifier() + const classifier = new Classifier(); - expect(() => classifier.predict([])).toThrow(Error) - }) + // @ts-expect-error Invalid type check + expect(() => classifier.predict([])).toThrow(Error); + }); test('should throw an error if maxMatches is not a number', () => { - const classifier = new Classifier() + const classifier = new Classifier(); - expect(() => classifier.predict('', 'test')).toThrow(Error) - }) + // @ts-expect-error Invalid type check + expect(() => classifier.predict('', 'test')).toThrow(Error); + }); test('should throw an error if minimumConfidence is not a number', () => { - const classifier = new Classifier() + const classifier = new Classifier(); - expect(() => classifier.predict('', undefined, 'test')).toThrow( - Error - ) - }) + // @ts-expect-error Invalid type check + expect(() => classifier.predict('', undefined, 'test')).toThrow(Error); + }); test('should throw an error if minimumConfidence is lower than 0', () => { - const classifier = new Classifier() + const classifier = new Classifier(); - expect(() => classifier.predict('', undefined, -1)).toThrow(Error) - }) + expect(() => classifier.predict('', undefined, -1)).toThrow(Error); + }); test('should throw an error if minimumConfidence is higher than 1', () => { - const classifier = new Classifier() + const classifier = new Classifier(); - expect(() => classifier.predict('', undefined, 2)).toThrow(Error) - }) + expect(() => classifier.predict('', undefined, 2)).toThrow(Error); + }); test('should return an array', () => { - const classifier = new Classifier() + const classifier = new Classifier(); - expect(classifier.predict('test')).toBeInstanceOf(Array) - }) + expect(classifier.predict('test')).toBeInstanceOf(Array); + }); test('should return one prediction when trained with a sample', () => { - const classifier = new Classifier() + const classifier = new Classifier(); - classifier.train('hello world', 'test') + classifier.train('hello world', 'test'); - expect(classifier.predict('hello world').length).toStrictEqual(1) - }) + expect(classifier.predict('hello world').length).toStrictEqual(1); + }); test('should not include predictions with a confidence below the configured minimumConfidence', () => { - const classifier = new Classifier() + const classifier = new Classifier(); - classifier.train('hello world', 'test') + classifier.train('hello world', 'test'); - const minimumConfidence = 0.8 + const minimumConfidence = 0.8; const predictions = classifier.predict( 'hello', undefined, - minimumConfidence - ) + minimumConfidence, + ); expect( predictions.filter((prediction) => { - return prediction.confidence < minimumConfidence - }).length - ).toStrictEqual(0) - }) + return prediction.confidence < minimumConfidence; + }).length, + ).toStrictEqual(0); + }); test('should not update the model vocabulary', () => { - const classifier = new Classifier() + const classifier = new Classifier(); - classifier.train('hello world', 'test') - classifier.predict('hello foo world') + classifier.train('hello world', 'test'); + classifier.predict('hello foo world'); - expect(classifier.model.vocabulary.has('foo')).toStrictEqual(false) - }) - }) -}) + assert(classifier.model.vocabulary); + expect(classifier.model.vocabulary.has('foo')).toStrictEqual(false); + }); + }); +}); diff --git a/test/Model.test.js b/test/Model.test.ts similarity index 55% rename from test/Model.test.js rename to test/Model.test.ts index 33fff16..a333470 100644 --- a/test/Model.test.js +++ b/test/Model.test.ts @@ -1,206 +1,208 @@ -import Model from '../src/Model' -import Vocabulary from '../src/Vocabulary' +import { describe, expect, test } from 'vitest'; + +import { Model } from '../src/Model.js'; +import { Vocabulary } from '../src/Vocabulary.js'; describe('Model', () => { describe('constructor', () => { test('should throw an error if config is not an object literal', () => { - expect(() => new Model([])).toThrow(Error) - }) + expect(() => new Model([])).toThrow(Error); + }); test('should throw an error if config option nGramMin is not a number', () => { expect( () => new Model({ - nGramMin: '' - }) - ).toThrow(Error) - }) + nGramMin: '', + }), + ).toThrow(Error); + }); test('should throw an error if config option nGramMax is not a number', () => { expect( () => new Model({ - nGramMax: '' - }) - ).toThrow(Error) - }) + nGramMax: '', + }), + ).toThrow(Error); + }); test('should throw an error if config option nGramMin is less than 1', () => { expect( () => new Model({ - nGramMin: 0 - }) - ).toThrow(Error) - }) + nGramMin: 0, + }), + ).toThrow(Error); + }); test('should throw an error if config option nGramMax is less than 1', () => { expect( () => new Model({ - nGramMax: 0 - }) - ).toThrow(Error) - }) + nGramMax: 0, + }), + ).toThrow(Error); + }); test('should throw an error if config option nGramMax is less than nGramMin', () => { expect( () => new Model({ nGramMin: 2, - nGramMax: 1 - }) - ).toThrow(Error) - }) + nGramMax: 1, + }), + ).toThrow(Error); + }); test('should throw an error if data is not an object literal', () => { expect( () => new Model({ - data: [] - }) - ).toThrow(Error) - }) - }) + data: [], + }), + ).toThrow(Error); + }); + }); describe('nGramMin', () => { test('should return a number', () => { - const model = new Model() + const model = new Model(); - expect(typeof model.nGramMin).toStrictEqual('number') - }) + expect(typeof model.nGramMin).toStrictEqual('number'); + }); test('should return the current nGramMin value', () => { const model = new Model({ nGramMin: 3, - nGramMax: 4 - }) + nGramMax: 4, + }); - expect(model.nGramMin).toStrictEqual(3) - }) + expect(model.nGramMin).toStrictEqual(3); + }); test('should set the nGramMin value', () => { - const model = new Model() + const model = new Model(); - model.nGramMin = 2 + model.nGramMin = 2; - expect(model.nGramMin).toStrictEqual(2) - }) + expect(model.nGramMin).toStrictEqual(2); + }); test('should throw an error if size is not an integer', () => { - const model = new Model() + const model = new Model(); expect(() => { - model.nGramMin = 1.1 - }).toThrow(Error) - }) - }) + model.nGramMin = 1.1; + }).toThrow(Error); + }); + }); describe('nGramMax', () => { test('should return a number', () => { - const model = new Model() + const model = new Model(); - expect(typeof model.nGramMax).toStrictEqual('number') - }) + expect(typeof model.nGramMax).toStrictEqual('number'); + }); test('should return the current nGramMax value', () => { const model = new Model({ - nGramMax: 2 - }) + nGramMax: 2, + }); - expect(model.nGramMax).toStrictEqual(2) - }) + expect(model.nGramMax).toStrictEqual(2); + }); test('should set the nGramMax value', () => { - const model = new Model() + const model = new Model(); - model.nGramMax = 3 + model.nGramMax = 3; - expect(model.nGramMax).toStrictEqual(3) - }) + expect(model.nGramMax).toStrictEqual(3); + }); test('should throw an error if size is not an integer', () => { - const model = new Model() + const model = new Model(); expect(() => { - model.nGramMax = 1.1 - }).toThrow(Error) - }) - }) + model.nGramMax = 1.1; + }).toThrow(Error); + }); + }); describe('vocabulary', () => { test('should return a vocabulary instance', () => { - const model = new Model() + const model = new Model(); - expect(model.vocabulary).toBeInstanceOf(Vocabulary) - }) + expect(model.vocabulary).toBeInstanceOf(Vocabulary); + }); test('should return false when vocabulary is configured to false', () => { const model = new Model({ - vocabulary: false - }) + vocabulary: false, + }); - expect(model.vocabulary).toStrictEqual(false) - }) + expect(model.vocabulary).toStrictEqual(false); + }); test('should set the vocabulary value when passing an array', () => { - const model = new Model() + const model = new Model(); - model.vocabulary = ['hello', 'world'] + model.vocabulary = ['hello', 'world']; expect(Array.from(model.vocabulary.terms)).toStrictEqual([ 'hello', - 'world' - ]) - }) + 'world', + ]); + }); test('should set the vocabulary value when passing false', () => { - const model = new Model() + const model = new Model(); - model.vocabulary = false + model.vocabulary = false; - expect(model.vocabulary).toStrictEqual(false) - }) - }) + expect(model.vocabulary).toStrictEqual(false); + }); + }); describe('data', () => { test('should return an object literal', () => { - const model = new Model() + const model = new Model(); - expect(model.data).toStrictEqual({}) - }) + expect(model.data).toStrictEqual({}); + }); test('should set the model data', () => { - const model = new Model() + const model = new Model(); model.data = { - test: { 0: 1 } - } + test: { 0: 1 }, + }; expect(model.data).toStrictEqual({ - test: { 0: 1 } - }) - }) + test: { 0: 1 }, + }); + }); test('should throw an error if data is not an object literal', () => { - const model = new Model() + const model = new Model(); expect(() => { - model.data = [] - }).toThrow(Error) - }) - }) + model.data = []; + }).toThrow(Error); + }); + }); describe('serialize', () => { test('should return an object literal created from the current model', () => { - const model = new Model() + const model = new Model(); expect(model.serialize()).toStrictEqual({ nGramMin: 1, nGramMax: 1, vocabulary: [], - data: {} - }) - }) - }) -}) + data: {}, + }); + }); + }); +}); diff --git a/test/Prediction.test.js b/test/Prediction.test.js deleted file mode 100644 index d2b3f84..0000000 --- a/test/Prediction.test.js +++ /dev/null @@ -1,73 +0,0 @@ -import Prediction from '../src/Prediction' - -describe('Prediction', () => { - describe('constructor', () => { - test('should throw an error if prediction is not an object literal', () => { - expect(() => new Prediction([])).toThrow(Error) - }) - }) - - describe('label', () => { - test('should throw an error if label is not a string', () => { - const prediction = new Prediction() - - expect(() => { - prediction.label = [] - }).toThrow(Error) - }) - - test('should return a string', () => { - const prediction = new Prediction() - - expect(typeof prediction.label).toStrictEqual('string') - }) - - test('should return the defined prediction label', () => { - const prediction = new Prediction({ - label: 'test' - }) - - expect(prediction.label).toStrictEqual('test') - }) - - test('should set the prediction label', () => { - const prediction = new Prediction() - - prediction.label = 'test' - - expect(prediction.label).toStrictEqual('test') - }) - }) - - describe('confidence', () => { - test('should throw an error if confidence is not a number', () => { - const prediction = new Prediction() - - expect(() => { - prediction.confidence = 'test' - }).toThrow(Error) - }) - - test('should return a number', () => { - const prediction = new Prediction() - - expect(typeof prediction.confidence).toStrictEqual('number') - }) - - test('should return the defined prediction confidence', () => { - const prediction = new Prediction({ - confidence: 0.5 - }) - - expect(prediction.confidence).toBeCloseTo(0.5) - }) - - test('should set the prediction confidence', () => { - const prediction = new Prediction() - - prediction.confidence = 1 - - expect(prediction.confidence).toStrictEqual(1) - }) - }) -}) diff --git a/test/Prediction.test.ts b/test/Prediction.test.ts new file mode 100644 index 0000000..5ac04bc --- /dev/null +++ b/test/Prediction.test.ts @@ -0,0 +1,74 @@ +import { describe, expect, test } from 'vitest'; +import { Prediction } from '../src/Prediction.js'; + +describe('Prediction', () => { + describe('constructor', () => { + test('should throw an error if prediction is not an object literal', () => { + expect(() => new Prediction([])).toThrow(Error); + }); + }); + + describe('label', () => { + test('should throw an error if label is not a string', () => { + const prediction = new Prediction(); + + expect(() => { + prediction.label = []; + }).toThrow(Error); + }); + + test('should return a string', () => { + const prediction = new Prediction(); + + expect(typeof prediction.label).toStrictEqual('string'); + }); + + test('should return the defined prediction label', () => { + const prediction = new Prediction({ + label: 'test', + }); + + expect(prediction.label).toStrictEqual('test'); + }); + + test('should set the prediction label', () => { + const prediction = new Prediction(); + + prediction.label = 'test'; + + expect(prediction.label).toStrictEqual('test'); + }); + }); + + describe('confidence', () => { + test('should throw an error if confidence is not a number', () => { + const prediction = new Prediction(); + + expect(() => { + prediction.confidence = 'test'; + }).toThrow(Error); + }); + + test('should return a number', () => { + const prediction = new Prediction(); + + expect(typeof prediction.confidence).toStrictEqual('number'); + }); + + test('should return the defined prediction confidence', () => { + const prediction = new Prediction({ + confidence: 0.5, + }); + + expect(prediction.confidence).toBeCloseTo(0.5); + }); + + test('should set the prediction confidence', () => { + const prediction = new Prediction(); + + prediction.confidence = 1; + + expect(prediction.confidence).toStrictEqual(1); + }); + }); +}); diff --git a/test/Vocabulary.test.js b/test/Vocabulary.test.js deleted file mode 100644 index 95ed3f7..0000000 --- a/test/Vocabulary.test.js +++ /dev/null @@ -1,177 +0,0 @@ -import Vocabulary from '../src/Vocabulary' - -describe('Vocabulary', () => { - describe('constructor', () => { - test('should throw an error if terms is not an array or set', () => { - expect(() => new Vocabulary({})).toThrow(Error) - }) - }) - - describe('size', () => { - test('should return a number', () => { - const vocabulary = new Vocabulary() - - expect(typeof vocabulary.size).toStrictEqual('number') - }) - - test('should return the vocabulary size', () => { - const vocabulary = new Vocabulary(['hello']) - - expect(vocabulary.size).toStrictEqual(1) - }) - }) - - describe('terms', () => { - test('should return a set instance', () => { - const vocabulary = new Vocabulary() - - expect(vocabulary.terms).toBeInstanceOf(Set) - }) - - test('should return the vocabulary terms', () => { - const vocabulary = new Vocabulary(['hello']) - - expect(Array.from(vocabulary.terms)).toStrictEqual(['hello']) - }) - - test('should set the vocabulary terms from an array', () => { - const vocabulary = new Vocabulary() - - vocabulary.terms = ['hello', 'world'] - - expect(Array.from(vocabulary.terms)).toStrictEqual([ - 'hello', - 'world' - ]) - }) - - test('should set the vocabulary terms from a set', () => { - const vocabulary = new Vocabulary() - - vocabulary.terms = new Set(['hello', 'world']) - - expect(Array.from(vocabulary.terms)).toStrictEqual([ - 'hello', - 'world' - ]) - }) - - test('should throw an error if terms is not an array or set', () => { - const vocabulary = new Vocabulary() - - expect(() => { - vocabulary.terms = {} - }).toThrow(Error) - }) - }) - - describe('add', () => { - test('should throw an error if terms is not a string, array or set', () => { - const vocabulary = new Vocabulary() - - expect(() => vocabulary.add({})).toThrow(Error) - }) - - test('should add a term to the vocabulary from a string', () => { - const vocabulary = new Vocabulary() - - vocabulary.add('test') - - expect(Array.from(vocabulary.terms)).toStrictEqual(['test']) - }) - - test('should add terms to the vocabulary from an array', () => { - const vocabulary = new Vocabulary() - - vocabulary.add(['hello', 'world']) - - expect(Array.from(vocabulary.terms)).toStrictEqual([ - 'hello', - 'world' - ]) - }) - - test('should add terms to the vocabulary from a set', () => { - const vocabulary = new Vocabulary() - - vocabulary.add(new Set(['hello', 'world'])) - - expect(Array.from(vocabulary.terms)).toStrictEqual([ - 'hello', - 'world' - ]) - }) - - test('should return vocabulary instance', () => { - const vocabulary = new Vocabulary() - - expect(vocabulary.add('test')).toBeInstanceOf(Vocabulary) - }) - }) - - describe('remove', () => { - test('should throw an error if terms is not a string, array or set', () => { - const vocabulary = new Vocabulary() - - expect(() => vocabulary.remove({})).toThrow(Error) - }) - - test('should remove a term to the vocabulary when called with a string', () => { - const vocabulary = new Vocabulary(['test']) - - vocabulary.remove('test') - - expect(Array.from(vocabulary.terms)).toStrictEqual([]) - }) - - test('should remove terms from the vocabulary when called with an array', () => { - const vocabulary = new Vocabulary(['hello', 'world']) - - vocabulary.remove(['world']) - - expect(Array.from(vocabulary.terms)).toStrictEqual(['hello']) - }) - - test('should remove terms from the vocabulary when called with a set', () => { - const vocabulary = new Vocabulary(['hello', 'world']) - - vocabulary.remove(new Set(['world'])) - - expect(Array.from(vocabulary.terms)).toStrictEqual(['hello']) - }) - - test('should return a vocabulary instance', () => { - const vocabulary = new Vocabulary(['test']) - - expect(vocabulary.remove('test')).toBeInstanceOf(Vocabulary) - }) - }) - - describe('has', () => { - test('should return a boolean', () => { - const vocabulary = new Vocabulary() - - expect(typeof vocabulary.has('test')).toStrictEqual('boolean') - }) - - test('should return whether a term exists in the vocabulary', () => { - const vocabulary = new Vocabulary(['test']) - - expect(vocabulary.has('test')).toStrictEqual(true) - }) - }) - - describe('indexOf', () => { - test('should return the index of an existing vocabulary term', () => { - const vocabulary = new Vocabulary(['test']) - - expect(vocabulary.indexOf('test')).toStrictEqual(0) - }) - - test('should return -1 for non-existing vocabulary terms', () => { - const vocabulary = new Vocabulary() - - expect(vocabulary.indexOf('test')).toStrictEqual(-1) - }) - }) -}) diff --git a/test/Vocabulary.test.ts b/test/Vocabulary.test.ts new file mode 100644 index 0000000..d89a15c --- /dev/null +++ b/test/Vocabulary.test.ts @@ -0,0 +1,170 @@ +import { describe, expect, test } from 'vitest'; +import { Vocabulary } from '../src/Vocabulary.js'; + +describe('Vocabulary', () => { + describe('constructor', () => { + test('should throw an error if terms is not an array or set', () => { + // @ts-expect-error Error check + expect(() => new Vocabulary({})).toThrow(Error); + }); + }); + + describe('size', () => { + test('should return a number', () => { + const vocabulary = new Vocabulary(); + + expect(typeof vocabulary.size).toStrictEqual('number'); + }); + + test('should return the vocabulary size', () => { + const vocabulary = new Vocabulary(['hello']); + + expect(vocabulary.size).toStrictEqual(1); + }); + }); + + describe('terms', () => { + test('should return a set instance', () => { + const vocabulary = new Vocabulary(); + + expect(vocabulary.terms).toBeInstanceOf(Set); + }); + + test('should return the vocabulary terms', () => { + const vocabulary = new Vocabulary(['hello']); + + expect(Array.from(vocabulary.terms)).toStrictEqual(['hello']); + }); + + test('should set the vocabulary terms from an array', () => { + const vocabulary = new Vocabulary(); + + vocabulary.terms = ['hello', 'world']; + + expect(Array.from(vocabulary.terms)).toStrictEqual(['hello', 'world']); + }); + + test('should set the vocabulary terms from a set', () => { + const vocabulary = new Vocabulary(); + + vocabulary.terms = new Set(['hello', 'world']); + + expect(Array.from(vocabulary.terms)).toStrictEqual(['hello', 'world']); + }); + + test('should throw an error if terms is not an array or set', () => { + const vocabulary = new Vocabulary(); + + expect(() => { + // @ts-expect-error Error check + vocabulary.terms = {}; + }).toThrow(Error); + }); + }); + + describe('add', () => { + test('should throw an error if terms is not a string, array or set', () => { + const vocabulary = new Vocabulary(); + + // @ts-expect-error Error check + expect(() => vocabulary.add({})).toThrow(Error); + }); + + test('should add a term to the vocabulary from a string', () => { + const vocabulary = new Vocabulary(); + + vocabulary.add('test'); + + expect(Array.from(vocabulary.terms)).toStrictEqual(['test']); + }); + + test('should add terms to the vocabulary from an array', () => { + const vocabulary = new Vocabulary(); + + vocabulary.add(['hello', 'world']); + + expect(Array.from(vocabulary.terms)).toStrictEqual(['hello', 'world']); + }); + + test('should add terms to the vocabulary from a set', () => { + const vocabulary = new Vocabulary(); + + vocabulary.add(new Set(['hello', 'world'])); + + expect(Array.from(vocabulary.terms)).toStrictEqual(['hello', 'world']); + }); + + test('should return vocabulary instance', () => { + const vocabulary = new Vocabulary(); + + expect(vocabulary.add('test')).toBeInstanceOf(Vocabulary); + }); + }); + + describe('remove', () => { + test('should throw an error if terms is not a string, array or set', () => { + const vocabulary = new Vocabulary(); + + // @ts-expect-error Error check + expect(() => vocabulary.remove({})).toThrow(Error); + }); + + test('should remove a term to the vocabulary when called with a string', () => { + const vocabulary = new Vocabulary(['test']); + + vocabulary.remove('test'); + + expect(Array.from(vocabulary.terms)).toStrictEqual([]); + }); + + test('should remove terms from the vocabulary when called with an array', () => { + const vocabulary = new Vocabulary(['hello', 'world']); + + vocabulary.remove(['world']); + + expect(Array.from(vocabulary.terms)).toStrictEqual(['hello']); + }); + + test('should remove terms from the vocabulary when called with a set', () => { + const vocabulary = new Vocabulary(['hello', 'world']); + + vocabulary.remove(new Set(['world'])); + + expect(Array.from(vocabulary.terms)).toStrictEqual(['hello']); + }); + + test('should return a vocabulary instance', () => { + const vocabulary = new Vocabulary(['test']); + + expect(vocabulary.remove('test')).toBeInstanceOf(Vocabulary); + }); + }); + + describe('has', () => { + test('should return a boolean', () => { + const vocabulary = new Vocabulary(); + + expect(typeof vocabulary.has('test')).toStrictEqual('boolean'); + }); + + test('should return whether a term exists in the vocabulary', () => { + const vocabulary = new Vocabulary(['test']); + + expect(vocabulary.has('test')).toStrictEqual(true); + }); + }); + + describe('indexOf', () => { + test('should return the index of an existing vocabulary term', () => { + const vocabulary = new Vocabulary(['test']); + + expect(vocabulary.indexOf('test')).toStrictEqual(0); + }); + + test('should return -1 for non-existing vocabulary terms', () => { + const vocabulary = new Vocabulary(); + + expect(vocabulary.indexOf('test')).toStrictEqual(-1); + }); + }); +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..5e805ba --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "noEmit": true, + "esModuleInterop": true, + "downlevelIteration": true, + "strict": true, + "lib": ["es2020", "dom"], + "moduleResolution": "NodeNext", + "module": "NodeNext" + }, + "include": ["src", "cli"], + "exclude": ["**/*.spec.*"] +} diff --git a/webpack.config.js b/webpack.config.js deleted file mode 100755 index 2aaeb03..0000000 --- a/webpack.config.js +++ /dev/null @@ -1,35 +0,0 @@ -require('core-js/stable') -require('regenerator-runtime/runtime') - -const path = require('path') - -module.exports = { - entry: { - 'index': './src/index.js' - }, - output: { - path: path.resolve(__dirname, 'lib'), - filename: 'index.js', - libraryTarget: 'umd', - globalObject: 'this' - }, - module: { - rules: [ - { - test: /\.js$/, - exclude: /node_modules/, - use: { - loader: 'babel-loader', - options: { - plugins: [ - ['@babel/plugin-transform-runtime', { - corejs: 3, - }] - ], - presets: ['@babel/preset-env'] - } - } - } - ] - } -}