diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a1b2739..e1aa11a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,11 +11,13 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node-version: [14, 16, 18, 20] + node-version: [18, 20, 22] steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - run: npm install + - run: npm run typecheck + - run: npm run build - run: npm test diff --git a/.gitignore b/.gitignore index 47bc001..a0a099e 100644 --- a/.gitignore +++ b/.gitignore @@ -140,3 +140,6 @@ fabric.properties .idea/sonarlint # End of https://www.gitignore.io/api/node,linux,webstorm + +# Build output +dist/ diff --git a/README.md b/README.md index b280ffa..618a4e9 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![NPM Licence shield](https://img.shields.io/github/license/Wozacosta/classificator.svg)](https://github.com/Wozacosta/classificator/blob/master/LICENSE) [![NPM release version shield](https://img.shields.io/npm/v/classificator.svg)](https://www.npmjs.com/package/classificator) -A fast, lightweight Naive Bayes classifier for Node.js with explainable predictions. +A fast, lightweight Naive Bayes classifier for Node.js with explainable predictions. Written in TypeScript with full type declarations. Ships dual CJS/ESM. ``` +-----------------+ @@ -42,17 +42,27 @@ More here: https://en.wikipedia.org/wiki/Naive_Bayes_classifier ## Installing -Recommended: Node v14.0.0 + +Recommended: Node v18.0.0 + ``` -npm install --save classificator +npm install classificator ``` ## Quick Start -```js +```ts +// ESM (recommended) +import bayes from 'classificator' + +// or with named imports +import { Naivebayes, fromJson } from 'classificator' + +// CJS (still works) const bayes = require('classificator') +``` + +```ts const classifier = bayes() // Train @@ -64,6 +74,36 @@ const result = classifier.categorize('awesome film') console.log(result.predictedCategory) // => 'positive' ``` +### TypeScript + +Full type declarations are included. All interfaces are exported: + +```ts +import bayes from 'classificator' +import type { + NaivebayesOptions, // constructor options + CategorizeResult, // return type of categorize() + Likelihood, // single category likelihood entry + InfluentialToken, // return type of topInfluentialTokens() + BatchItem, // { text, category } for learnBatch() + CategoryStats, // per-category stats + CategoryStatsResult, // return type of getCategoryStats() +} from 'classificator' + +const options: NaivebayesOptions = { alpha: 0.5, fitPrior: false } +const classifier = bayes(options) + +classifier.learn('great movie', 'positive') +const result: CategorizeResult = classifier.categorize('great') +``` + +You can also import the class directly: + +```ts +import { Naivebayes } from 'classificator' +const classifier = new Naivebayes({ alpha: 0.5 }) +``` + ## How It Works @@ -478,7 +518,7 @@ const classifier = bayes.fromJson(saved, { tokenizer: myTokenizer }) ## Test Suite -The library includes a comprehensive test suite with **109 tests**: +The library includes a comprehensive test suite with **121 tests** (powered by Vitest): ``` Unit tests (82) - Individual method correctness, edge cases, @@ -492,6 +532,9 @@ The library includes a comprehensive test suite with **109 tests**: analysis, multi-category topic classification, incremental learning, mistake correction, imbalanced dataset handling + + Dist tests (12) - Verify compiled output: CJS require, ESM import, + named exports, type declarations, round-trips ``` Run with: @@ -506,6 +549,23 @@ npm test ## Changelog +### 1.0.0 + +**TypeScript rewrite:** +- Full TypeScript source with exported types (`NaivebayesOptions`, `CategorizeResult`, `Likelihood`, `InfluentialToken`, `CategoryStats`, `BatchItem`) +- Dual CJS/ESM output via tsup — `require()` and `import` both work +- Type declarations (`.d.ts`) included for TypeScript consumers +- ES6 class-based implementation (same API, better types) + +**Modern tooling:** +- Build: tsup (esbuild-based, fast) +- Test: Vitest (replaces Mocha) +- CI: Node 18/20/22 with typecheck + build + test steps + +**Breaking changes:** +- Minimum Node version raised to 18.0.0 (14 and 16 are EOL) +- Named ESM imports available: `import { Naivebayes, fromJson } from 'classificator'` + ### 0.5.0 **New features:** @@ -533,7 +593,7 @@ npm test - Tokenizer and tokenPreprocessor validation at construction time - `getCategoryStats()` now includes `wordCount` in `_total` - GitHub Actions CI for Node 14/16/18/20 -- Comprehensive test suite (109 tests: unit + integration + E2E) +- Comprehensive test suite (121 tests: unit + integration + E2E + dist) - Improved JSDoc and README documentation with diagrams ### 0.4.0 diff --git a/lib/classificator.js b/lib/classificator.js deleted file mode 100644 index af650a2..0000000 --- a/lib/classificator.js +++ /dev/null @@ -1,569 +0,0 @@ -const Decimal = require('decimal.js').default; - -/* - Expose our naive-bayes generator function - */ - -module.exports = function(options) { - return new Naivebayes(options); -}; - -// keys we use to serialize a classifier's state -const STATE_KEYS = (module.exports.STATE_KEYS = [ - 'categories', - 'docCount', - 'totalDocuments', - 'vocabulary', - 'vocabularySize', - 'wordCount', - 'wordFrequencyCount', - 'options', -]); -const DEFAULT_ALPHA = 1; -const DEFAULT_FIT_PRIOR = true; - -/** - * Initializes a NaiveBayes instance from a JSON state representation. - * Use this with classifier.toJson(). - * - * @param {String|Object} jsonStrOrObject state representation obtained by classifier.toJson() - * @param {Object} [options] optional options object (e.g. { tokenizer: fn }) - * @return {NaiveBayes} Classifier - * @throws {Error} If input is not a valid JSON string or object, or if required state keys are missing. - */ -module.exports.fromJson = (jsonStrOrObject, options) => { - let parameters; - - try { - switch (typeof jsonStrOrObject) { - case 'string': - parameters = JSON.parse(jsonStrOrObject); - break; - - case 'object': - if (jsonStrOrObject === null) { - throw new Error(''); - } - parameters = jsonStrOrObject; - break; - - default: - throw new Error(''); - } - } catch (e) { - throw new Error('NaiveBayes.fromJson expects a valid JSON string or an object.'); - } - - // merge any runtime-only options (e.g. tokenizer) into the restored options - const restoredOptions = Object.assign({}, parameters.options, options); - - // init a new classifier - const classifier = new Naivebayes(restoredOptions); - - // override the classifier's state - STATE_KEYS.forEach((k) => { - if (typeof parameters[k] === 'undefined') { - throw new Error( - `NaiveBayes.fromJson: JSON string is missing an expected property: [${k}].` - ); - } - classifier[k] = parameters[k]; - }); - - // restore the merged options (STATE_KEYS includes 'options' which overwrites - // with the saved state, losing runtime-only options like tokenizer/tokenPreprocessor) - classifier.options = restoredOptions; - - return classifier; -}; - -/** - * Given an input string, tokenize it into an array of word tokens. - * This is the default tokenization function used if user does not provide one in `options`. - * - * @param {String} text - * @return {Array} - */ -const defaultTokenizer = (text) => { - const rgxPunctuation = /[^(a-zA-ZA-Яa-я0-9_)+\s]/g; - const sanitized = text.replace(rgxPunctuation, ' '); - return sanitized.split(/\s+/).filter(token => token.length > 0); -}; - -/** - * Naive-Bayes Classifier - * - * This is a naive-bayes classifier that uses Laplace Smoothing. - * - * @param {Object} [options] Configuration options - * @param {Function} [options.tokenizer] Custom tokenization function. Receives a string, - * must return an array of string tokens. - * @param {Function} [options.tokenPreprocessor] Optional function to transform tokens after - * tokenization (e.g. stopword removal, stemming). - * Receives an array of tokens, must return an array. - * @param {number} [options.alpha=1] Additive (Laplace) smoothing parameter. - * Higher values = more conservative predictions. - * Set to 0 to disable smoothing. - * @param {boolean} [options.fitPrior=true] Whether to use learned prior probabilities. - * When false, uses uniform prior (all categories - * equally likely before seeing the text). - * @throws {TypeError} If options is truthy but not a plain object, or tokenizer/tokenPreprocessor is not a function. - */ -function Naivebayes(options) { - this.options = {}; - if (typeof options !== 'undefined') { - if (!options || typeof options !== 'object' || Array.isArray(options)) { - throw TypeError( - `NaiveBayes got invalid 'options': '${options}'. Pass in an object.` - ); - } - this.options = options; - } - - if (this.options.tokenizer && typeof this.options.tokenizer !== 'function') { - throw TypeError('NaiveBayes: tokenizer must be a function.'); - } - if (this.options.tokenPreprocessor && typeof this.options.tokenPreprocessor !== 'function') { - throw TypeError('NaiveBayes: tokenPreprocessor must be a function.'); - } - - this.tokenizer = this.options.tokenizer || defaultTokenizer; - this.tokenPreprocessor = this.options.tokenPreprocessor || null; - this.alpha = this.options.alpha === undefined ? DEFAULT_ALPHA : this.options.alpha; - this.fitPrior = this.options.fitPrior === undefined ? DEFAULT_FIT_PRIOR : this.options.fitPrior; - - this.vocabulary = {}; - this.vocabularySize = 0; - this.totalDocuments = 0; - this.docCount = {}; - this.wordCount = {}; - this.wordFrequencyCount = {}; - this.categories = {}; -} - -/** - * Tokenize text and optionally apply the preprocessor. - * - * @param {String} text - * @return {Array} tokens - */ -Naivebayes.prototype.tokenize = function(text) { - const tokens = this.tokenizer(text); - if (this.tokenPreprocessor) { - return this.tokenPreprocessor(tokens); - } - return tokens; -}; - -/** - * Initialize each of our data structure entries for this new category. - * - * @param {String} categoryName - * @return {Naivebayes} this - */ -Naivebayes.prototype.initializeCategory = function(categoryName) { - if (!this.categories[categoryName]) { - this.docCount[categoryName] = 0; - this.wordCount[categoryName] = 0; - this.wordFrequencyCount[categoryName] = {}; - this.categories[categoryName] = true; - } - return this; -}; - -/** - * Properly remove a category, unlearning all words that were associated to it. - * - * @param {String} categoryName - * @return {Naivebayes} this - */ -Naivebayes.prototype.removeCategory = function(categoryName) { - if (!this.categories[categoryName]) { - return this; - } - this.totalDocuments -= this.docCount[categoryName]; - - Object.keys(this.wordFrequencyCount[categoryName]).forEach((token) => { - if (this.vocabulary[token] && this.vocabulary[token] > 0) { - this.vocabulary[token]--; - if (this.vocabulary[token] === 0) this.vocabularySize--; - } - }); - - delete this.docCount[categoryName]; - delete this.wordCount[categoryName]; - delete this.wordFrequencyCount[categoryName]; - delete this.categories[categoryName]; - - return this; -}; - -/** - * Train our naive-bayes classifier by telling it what `category` - * the `text` corresponds to. - * - * @param {String} text - * @param {String} category Category to learn as being text - * @return {Naivebayes} this - * @throws {TypeError} If text or category is not a string. - */ -Naivebayes.prototype.learn = function(text, category) { - if (typeof text !== 'string') { - throw new TypeError(`NaiveBayes: text must be a string, got ${typeof text}.`); - } - if (typeof category !== 'string') { - throw new TypeError(`NaiveBayes: category must be a string, got ${typeof category}.`); - } - - this.initializeCategory(category); - - this.docCount[category]++; - this.totalDocuments++; - - const tokens = this.tokenize(text); - const frequencyTable = this.frequencyTable(tokens); - - Object.keys(frequencyTable).forEach((token) => { - const frequencyInText = frequencyTable[token]; - - if (!this.vocabulary[token] || this.vocabulary[token] === 0) { - this.vocabularySize++; - this.vocabulary[token] = 1; - } else { - this.vocabulary[token]++; - } - - if (!this.wordFrequencyCount[category][token]) { - this.wordFrequencyCount[category][token] = frequencyInText; - } else this.wordFrequencyCount[category][token] += frequencyInText; - - this.wordCount[category] += frequencyInText; - }); - - return this; -}; - -/** - * Untrain our naive-bayes classifier by telling it what `category` - * the `text` to remove corresponds to. - * - * @param {String} text - * @param {String} category Category to unlearn as being text - * @return {Naivebayes} this - * @throws {TypeError} If text or category is not a string. - * @throws {Error} If category does not exist. - */ -Naivebayes.prototype.unlearn = function(text, category) { - if (typeof text !== 'string') { - throw new TypeError(`NaiveBayes: text must be a string, got ${typeof text}.`); - } - if (typeof category !== 'string') { - throw new TypeError(`NaiveBayes: category must be a string, got ${typeof category}.`); - } - if (!this.categories[category]) { - throw new Error(`NaiveBayes: cannot unlearn from non-existent category: '${category}'.`); - } - - this.docCount[category]--; - if (this.docCount[category] === 0) { - delete this.docCount[category]; - } - - this.totalDocuments--; - - const tokens = this.tokenize(text); - const frequencyTable = this.frequencyTable(tokens); - - Object.keys(frequencyTable).forEach((token) => { - const frequencyInText = frequencyTable[token]; - - if (this.vocabulary[token] && this.vocabulary[token] > 0) { - this.vocabulary[token]--; - if (this.vocabulary[token] === 0) this.vocabularySize--; - } - - if (this.wordFrequencyCount[category] && this.wordFrequencyCount[category][token]) { - this.wordFrequencyCount[category][token] -= frequencyInText; - if (this.wordFrequencyCount[category][token] <= 0) { - delete this.wordFrequencyCount[category][token]; - } - } - - if (this.wordCount[category] !== undefined) { - this.wordCount[category] -= frequencyInText; - if (this.wordCount[category] <= 0) { - delete this.wordCount[category]; - delete this.wordFrequencyCount[category]; - } - } - }); - - // clean up category if no documents remain - if (!this.docCount[category]) { - delete this.categories[category]; - } - - return this; -}; - -/** - * Determine what category `text` belongs to. - * - * @param {String} text - * @return {Object} The predicted category, and the likelihoods stats. - * @throws {TypeError} If text is not a string. - */ -Naivebayes.prototype.categorize = function(text) { - if (typeof text !== 'string') { - throw new TypeError(`NaiveBayes: text must be a string, got ${typeof text}.`); - } - - const tokens = this.tokenize(text); - const frequencyTable = this.frequencyTable(tokens); - const categories = Object.keys(this.categories); - const likelihoods = []; - - if (categories.length === 0) { - return { - likelihoods: [], - predictedCategory: null - }; - } - - categories.forEach((category) => { - let categoryLikelihood; - if (this.fitPrior) { - categoryLikelihood = this.docCount[category] / this.totalDocuments; - } else { - categoryLikelihood = 1; - } - - let logLikelihood = Decimal(categoryLikelihood); - logLikelihood = logLikelihood.naturalLogarithm(); - - Object.keys(frequencyTable).forEach((token) => { - if (this.vocabulary[token] && this.vocabulary[token] > 0) { - const termFrequencyInText = frequencyTable[token]; - const tokenProbability = this.tokenProbability(token, category); - - let logTokenProbability = Decimal(tokenProbability); - logTokenProbability = logTokenProbability.naturalLogarithm(); - logLikelihood = logLikelihood.plus(logTokenProbability.times(termFrequencyInText)); - } - }); - - likelihoods.push({ category, logLikelihood }); - }); - - // Numerically stable logsumexp: subtract max to prevent overflow/underflow - const logsumexp = (likelihoods) => { - if (likelihoods.length === 0) return new Decimal(0); - - const maxLog = likelihoods.reduce((max, l) => { - const val = l.logLikelihood; - return val.greaterThan(max) ? val : max; - }, likelihoods[0].logLikelihood); - - let sum = new Decimal(0); - likelihoods.forEach((likelihood) => { - const shifted = likelihood.logLikelihood.minus(maxLog); - sum = sum.plus(Decimal.exp(shifted)); - }); - - return maxLog.plus(sum.naturalLogarithm()); - }; - - const logProbX = logsumexp(likelihoods); - likelihoods.forEach((likelihood) => { - likelihood.logProba = Decimal(likelihood.logLikelihood).minus(logProbX); - likelihood.proba = likelihood.logProba.naturalExponential(); - likelihood.logProba = likelihood.logProba.toNumber(); - likelihood.proba = likelihood.proba.toNumber(); - likelihood.logLikelihood = likelihood.logLikelihood.toNumber(); - }); - - likelihoods.sort((a, b) => b.proba - a.proba); - - return { - likelihoods, - predictedCategory: likelihoods[0].category - }; -}; - -/** - * Like categorize(), but returns only the top N most likely categories. - * - * @param {String} text The text to categorize. - * @param {number} n Maximum number of categories to return. - * @return {Object} Same shape as categorize(), but with truncated likelihoods. - */ -Naivebayes.prototype.categorizeTopN = function(text, n) { - const result = this.categorize(text); - if (result.likelihoods.length > n) { - result.likelihoods = result.likelihoods.slice(0, n); - } - return result; -}; - -/** - * Categorize with a confidence threshold. Returns null predictedCategory - * if the top category's probability is below the threshold. - * - * @param {String} text - * @param {number} threshold Minimum probability (0 to 1) for a confident prediction. - * @return {Object} Same shape as categorize(), but predictedCategory is null if below threshold. - */ -Naivebayes.prototype.categorizeWithConfidence = function(text, threshold) { - if (typeof threshold !== 'number' || threshold < 0 || threshold > 1) { - throw new TypeError('NaiveBayes: threshold must be a number between 0 and 1.'); - } - const result = this.categorize(text); - if (result.predictedCategory === null) return result; - - if (result.likelihoods[0].proba < threshold) { - result.predictedCategory = null; - } - return result; -}; - -/** - * Get the top N most influential tokens for a given text's classification. - * Shows which words most contributed to the predicted category. - * - * @param {String} text - * @param {number} [n=5] Number of top tokens to return. - * @return {Array<{token: string, probability: number, frequency: number}>} - */ -Naivebayes.prototype.topInfluentialTokens = function(text, n) { - n = (n === undefined || n === null) ? 5 : Math.max(0, Math.floor(n)); - const tokens = this.tokenize(text); - const frequencyTable = this.frequencyTable(tokens); - const result = this.categorize(text); - const topCategory = result.predictedCategory; - - if (!topCategory) return []; - - return Object.keys(frequencyTable) - .filter(token => this.vocabulary[token] && this.vocabulary[token] > 0) - .map(token => ({ - token, - probability: this.tokenProbability(token, topCategory), - frequency: frequencyTable[token] - })) - .sort((a, b) => b.probability - a.probability) - .slice(0, n); -}; - -/** - * Calculate probability that a `token` belongs to a `category`. - * Uses Laplace smoothing. If the token was never seen in the category, - * still returns a non-zero probability due to smoothing. - * - * @param {String} token - * @param {String} category - * @return {Number} probability (0 < p <= 1, depending on alpha) - */ -Naivebayes.prototype.tokenProbability = function(token, category) { - const wordFrequencyCount = this.wordFrequencyCount[category][token] || 0; - const wordCount = this.wordCount[category]; - - return (wordFrequencyCount + this.alpha) / (wordCount + this.alpha * this.vocabularySize); -}; - -/** - * Build a frequency hashmap where - * - the keys are the entries in `tokens` - * - the values are the frequency of each entry in `tokens` - * - * @param {Array} tokens Normalized word array - * @return {Object} - */ -Naivebayes.prototype.frequencyTable = function(tokens) { - const frequencyTable = Object.create(null); - - tokens.forEach((token) => { - if (!frequencyTable[token]) frequencyTable[token] = 1; - else frequencyTable[token]++; - }); - - return frequencyTable; -}; - -/** - * Dump the classifier's state as a JSON string. - * - * @return {String} Representation of the classifier. - */ -Naivebayes.prototype.toJson = function() { - const state = {}; - STATE_KEYS.forEach(k => (state[k] = this[k])); - return JSON.stringify(state); -}; - -/** - * Get an array of all category names the classifier has learned. - * - * @return {String[]} Array of category name strings. - */ -Naivebayes.prototype.getCategories = function() { - return Object.keys(this.categories); -}; - -/** - * Learn from multiple text/category pairs at once. - * - * @param {Array<{text: string, category: string}>} items Array of training items. - * @return {Naivebayes} this - * @throws {TypeError} If items is not an array. - */ -Naivebayes.prototype.learnBatch = function(items) { - if (!Array.isArray(items)) { - throw new TypeError('NaiveBayes: learnBatch expects an array of { text, category } objects.'); - } - items.forEach(item => { - this.learn(item.text, item.category); - }); - return this; -}; - -/** - * Reset the classifier to its initial (untrained) state, preserving configuration options. - * - * @return {Naivebayes} this - */ -Naivebayes.prototype.reset = function() { - this.vocabulary = {}; - this.vocabularySize = 0; - this.totalDocuments = 0; - this.docCount = {}; - this.wordCount = {}; - this.wordFrequencyCount = {}; - this.categories = {}; - return this; -}; - -/** - * Get statistics about each category's training data. - * - * @return {Object} Map of category names to { docCount, wordCount, vocabularySize }, - * plus a _total key with aggregate stats. - */ -Naivebayes.prototype.getCategoryStats = function() { - const stats = {}; - Object.keys(this.categories).forEach(category => { - stats[category] = { - docCount: this.docCount[category] || 0, - wordCount: this.wordCount[category] || 0, - vocabularySize: Object.keys(this.wordFrequencyCount[category] || {}).length - }; - }); - const totalWordCount = Object.keys(this.categories).reduce((sum, cat) => { - return sum + (this.wordCount[cat] || 0); - }, 0); - stats._total = { - docCount: this.totalDocuments, - wordCount: totalWordCount, - vocabularySize: this.vocabularySize - }; - return stats; -}; diff --git a/package-lock.json b/package-lock.json index abbca28..2164a90 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,1080 +1,2048 @@ { "name": "classificator", - "version": "0.3.4", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "classificator", - "version": "0.3.4", + "version": "1.0.0", "dependencies": { "decimal.js": "^10.0.0" }, "devDependencies": { - "mocha": "^9.0.2" + "@types/node": "^22.0.0", + "tsup": "^8.5.0", + "typescript": "^5.8.0", + "vitest": "^3.0.0" }, "engines": { - "node": ">=5.0.0" + "node": ">=18.0.0" } }, - "node_modules/@ungap/promise-all-settled": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", - "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", - "dev": true - }, - "node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz", + "integrity": "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==", + "cpu": [ + "ppc64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], "engines": { - "node": ">=6" + "node": ">=18" } }, - "node_modules/ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "node_modules/@esbuild/android-arm": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz", + "integrity": "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==", + "cpu": [ + "arm" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=4" + "node": ">=18" } }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@esbuild/android-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz", + "integrity": "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=18" } }, - "node_modules/anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "node_modules/@esbuild/android-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz", + "integrity": "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">= 8" + "node": ">=18" } }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz", + "integrity": "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==", + "cpu": [ + "arm64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz", + "integrity": "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" } }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz", + "integrity": "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "node_modules/camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz", + "integrity": "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==", + "cpu": [ + "x64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=18" } }, - "node_modules/chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "node_modules/@esbuild/linux-arm": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz", + "integrity": "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==", + "cpu": [ + "arm" + ], "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=18" } }, - "node_modules/chalk/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz", + "integrity": "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/chokidar": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", - "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz", + "integrity": "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==", + "cpu": [ + "ia32" + ], "dev": true, - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" + "node": ">=18" } }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz", + "integrity": "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==", + "cpu": [ + "loong64" + ], "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz", + "integrity": "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==", + "cpu": [ + "mips64el" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/cliui/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz", + "integrity": "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==", + "cpu": [ + "ppc64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/cliui/node_modules/string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz", + "integrity": "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==", + "cpu": [ + "riscv64" + ], "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz", + "integrity": "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==", + "cpu": [ + "s390x" + ], "dev": true, - "dependencies": { - "ansi-regex": "^5.0.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/@esbuild/linux-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz", + "integrity": "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=7.0.0" + "node": ">=18" } }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "node_modules/debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz", + "integrity": "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "ms": "2.1.2" - }, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=18" } }, - "node_modules/debug/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz", + "integrity": "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz", + "integrity": "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==", + "cpu": [ + "arm64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=18" } }, - "node_modules/decimal.js": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.0.1.tgz", - "integrity": "sha512-vklWB5C4Cj423xnaOtsUmAv0/7GqlXIgDv2ZKDyR64OV3OSzGHNx2mk4p/1EKnB5s70k73cIOOEcG9YzF0q4Lw==" + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz", + "integrity": "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz", + "integrity": "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==", + "cpu": [ + "arm64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], "engines": { - "node": ">=0.3.1" + "node": ">=18" } }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz", + "integrity": "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz", + "integrity": "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==", + "cpu": [ + "arm64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=6" + "node": ">=18" } }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz", + "integrity": "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==", + "cpu": [ + "ia32" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=18" } }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "node_modules/@esbuild/win32-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz", + "integrity": "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dev": true, + "license": "MIT", "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6.0.0" } }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true, - "bin": { - "flat": "cli.js" + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.0.tgz", + "integrity": "sha512-WOhNW9K8bR3kf4zLxbfg6Pxu2ybOUbB2AjMDHSQx86LIF4rH4Ft7vmMwNt0loO0eonglSNy4cpD3MKXXKQu0/A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.0.tgz", + "integrity": "sha512-u6JHLll5QKRvjciE78bQXDmqRqNs5M/3GVqZeMwvmjaNODJih/WIrJlFVEihvV0MiYFmd+ZyPr9wxOVbPAG2Iw==", + "cpu": [ + "arm64" + ], "dev": true, - "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.0.tgz", + "integrity": "sha512-qEF7CsKKzSRc20Ciu2Zw1wRrBz4g56F7r/vRwY430UPp/nt1x21Q/fpJ9N5l47WWvJlkNCPJz3QRVw008fi7yA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.0.tgz", + "integrity": "sha512-WADYozJ4QCnXCH4wPB+3FuGmDPoFseVCUrANmA5LWwGmC6FL14BWC7pcq+FstOZv3baGX65tZ378uT6WG8ynTw==", + "cpu": [ + "x64" ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.0.tgz", + "integrity": "sha512-6b8wGHJlDrGeSE3aH5mGNHBjA0TTkxdoNHik5EkvPHCt351XnigA4pS7Wsj/Eo9Y8RBU6f35cjN9SYmCFBtzxw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.0.tgz", + "integrity": "sha512-h25Ga0t4jaylMB8M/JKAyrvvfxGRjnPQIR8lnCayyzEjEOx2EJIlIiMbhpWxDRKGKF8jbNH01NnN663dH638mA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.0.tgz", + "integrity": "sha512-RzeBwv0B3qtVBWtcuABtSuCzToo2IEAIQrcyB/b2zMvBWVbjo8bZDjACUpnaafaxhTw2W+imQbP2BD1usasK4g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.0.tgz", + "integrity": "sha512-Sf7zusNI2CIU1HLzuu9Tc5YGAHEZs5Lu7N1ssJG4Tkw6e0MEsN7NdjUDDfGNHy2IU+ENyWT+L2obgWiguWibWQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.0.tgz", + "integrity": "sha512-DX2x7CMcrJzsE91q7/O02IJQ5/aLkVtYFryqCjduJhUfGKG6yJV8hxaw8pZa93lLEpPTP/ohdN4wFz7yp/ry9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.0.tgz", + "integrity": "sha512-09EL+yFVbJZlhcQfShpswwRZ0Rg+z/CsSELFCnPt3iK+iqwGsI4zht3secj5vLEs957QvFFXnzAT0FFPIxSrkQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.0.tgz", + "integrity": "sha512-i9IcCMPr3EXm8EQg5jnja0Zyc1iFxJjZWlb4wr7U2Wx/GrddOuEafxRdMPRYVaXjgbhvqalp6np07hN1w9kAKw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.0.tgz", + "integrity": "sha512-DGzdJK9kyJ+B78MCkWeGnpXJ91tK/iKA6HwHxF4TAlPIY7GXEvMe8hBFRgdrR9Ly4qebR/7gfUs9y2IoaVEyog==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.0.tgz", + "integrity": "sha512-RwpnLsqC8qbS8z1H1AxBA1H6qknR4YpPR9w2XX0vo2Sz10miu57PkNcnHVaZkbqyw/kUWfKMI73jhmfi9BRMUQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.0.tgz", + "integrity": "sha512-Z8pPf54Ly3aqtdWC3G4rFigZgNvd+qJlOE52fmko3KST9SoGfAdSRCwyoyG05q1HrrAblLbk1/PSIV+80/pxLg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.0.tgz", + "integrity": "sha512-3a3qQustp3COCGvnP4SvrMHnPQ9d1vzCakQVRTliaz8cIp/wULGjiGpbcqrkv0WrHTEp8bQD/B3HBjzujVWLOA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.0.tgz", + "integrity": "sha512-pjZDsVH/1VsghMJ2/kAaxt6dL0psT6ZexQVrijczOf+PeP2BUqTHYejk3l6TlPRydggINOeNRhvpLa0AYpCWSQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.0.tgz", + "integrity": "sha512-3ObQs0BhvPgiUVZrN7gqCSvmFuMWvWvsjG5ayJ3Lraqv+2KhOsp+pUbigqbeWqueGIsnn+09HBw27rJ+gYK4VQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.0.tgz", + "integrity": "sha512-EtylprDtQPdS5rXvAayrNDYoJhIz1/vzN2fEubo3yLE7tfAw+948dO0g4M0vkTVFhKojnF+n6C8bDNe+gDRdTg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.0.tgz", + "integrity": "sha512-k09oiRCi/bHU9UVFqD17r3eJR9bn03TyKraCrlz5ULFJGdJGi7VOmm9jl44vOJvRJ6P7WuBi/s2A97LxxHGIdw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.0.tgz", + "integrity": "sha512-1o/0/pIhozoSaDJoDcec+IVLbnRtQmHwPV730+AOD29lHEEo4F5BEUB24H0OBdhbBBDwIOSuf7vgg0Ywxdfiiw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.0.tgz", + "integrity": "sha512-pESDkos/PDzYwtyzB5p/UoNU/8fJo68vcXM9ZW2V0kjYayj1KaaUfi1NmTUTUpMn4UhU4gTuK8gIaFO4UGuMbA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.0.tgz", + "integrity": "sha512-hj1wFStD7B1YBeYmvY+lWXZ7ey73YGPcViMShYikqKT1GtstIKQAtfUI6yrzPjAy/O7pO0VLXGmUVWXQMaYgTQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.0.tgz", + "integrity": "sha512-SyaIPFoxmUPlNDq5EHkTbiKzmSEmq/gOYFI/3HHJ8iS/v1mbugVa7dXUzcJGQfoytp9DJFLhHH4U3/eTy2Bq4w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.0.tgz", + "integrity": "sha512-RdcryEfzZr+lAr5kRm2ucN9aVlCCa2QNq4hXelZxb8GG0NJSazq44Z3PCCc8wISRuCVnGs0lQJVX5Vp6fKA+IA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.0.tgz", + "integrity": "sha512-PrsWNQ8BuE00O3Xsx3ALh2Df8fAj9+cvvX9AIA6o4KpATR98c9mud4XtDWVvsEuyia5U4tVSTKygawyJkjm60w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" } }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.19.15", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.15.tgz", + "integrity": "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" } }, - "node_modules/glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "node_modules/@vitest/expect": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", + "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", "dev": true, + "license": "MIT", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://opencollective.com/vitest" } }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "node_modules/@vitest/mocker": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", + "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", "dev": true, + "license": "MIT", "dependencies": { - "is-glob": "^4.0.1" + "@vitest/spy": "3.2.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" }, - "engines": { - "node": ">= 6" + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } } }, - "node_modules/growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "node_modules/@vitest/pretty-format": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", + "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", "dev": true, - "engines": { - "node": ">=4.x" + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/@vitest/runner": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", + "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", "dev": true, - "engines": { - "node": ">=8" + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.4", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "node_modules/@vitest/snapshot": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", + "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", "dev": true, - "bin": { - "he": "bin/he" + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "node_modules/@vitest/spy": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", + "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", "dev": true, + "license": "MIT", "dependencies": { - "once": "^1.3.0", - "wrappy": "1" + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "node_modules/@vitest/utils": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", + "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", "dev": true, + "license": "MIT", "dependencies": { - "binary-extensions": "^2.0.0" + "@vitest/pretty-format": "3.2.4", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" }, "engines": { - "node": ">=8" + "node": ">=0.4.0" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true, + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=12" } }, - "node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "node_modules/bundle-require": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-5.1.0.tgz", + "integrity": "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==", "dev": true, + "license": "MIT", + "dependencies": { + "load-tsconfig": "^0.2.3" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "peerDependencies": { + "esbuild": ">=0.18" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=4" + "node": ">=8" } }, - "node_modules/is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", "dev": true, + "license": "MIT", "dependencies": { - "is-extglob": "^2.1.1" + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=18" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", "dev": true, + "license": "MIT", "engines": { - "node": ">=0.12.0" + "node": ">= 16" } }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 6" } }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", "dev": true, + "license": "MIT" + }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=10" + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true + "node_modules/decimal.js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.0.1.tgz", + "integrity": "sha512-vklWB5C4Cj423xnaOtsUmAv0/7GqlXIgDv2ZKDyR64OV3OSzGHNx2mk4p/1EKnB5s70k73cIOOEcG9YzF0q4Lw==" }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz", + "integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.4", + "@esbuild/android-arm": "0.27.4", + "@esbuild/android-arm64": "0.27.4", + "@esbuild/android-x64": "0.27.4", + "@esbuild/darwin-arm64": "0.27.4", + "@esbuild/darwin-x64": "0.27.4", + "@esbuild/freebsd-arm64": "0.27.4", + "@esbuild/freebsd-x64": "0.27.4", + "@esbuild/linux-arm": "0.27.4", + "@esbuild/linux-arm64": "0.27.4", + "@esbuild/linux-ia32": "0.27.4", + "@esbuild/linux-loong64": "0.27.4", + "@esbuild/linux-mips64el": "0.27.4", + "@esbuild/linux-ppc64": "0.27.4", + "@esbuild/linux-riscv64": "0.27.4", + "@esbuild/linux-s390x": "0.27.4", + "@esbuild/linux-x64": "0.27.4", + "@esbuild/netbsd-arm64": "0.27.4", + "@esbuild/netbsd-x64": "0.27.4", + "@esbuild/openbsd-arm64": "0.27.4", + "@esbuild/openbsd-x64": "0.27.4", + "@esbuild/openharmony-arm64": "0.27.4", + "@esbuild/sunos-x64": "0.27.4", + "@esbuild/win32-arm64": "0.27.4", + "@esbuild/win32-ia32": "0.27.4", + "@esbuild/win32-x64": "0.27.4" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", "dependencies": { - "argparse": "^2.0.1" + "@types/estree": "^1.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "node_modules/fix-dts-default-cjs-exports": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fix-dts-default-cjs-exports/-/fix-dts-default-cjs-exports-1.0.1.tgz", + "integrity": "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==", "dev": true, + "license": "MIT", "dependencies": { - "p-locate": "^5.0.0" - }, + "magic-string": "^0.30.17", + "mlly": "^1.7.4", + "rollup": "^4.34.8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, + "license": "MIT", "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } + "license": "MIT" }, - "node_modules/mocha": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.0.2.tgz", - "integrity": "sha512-FpspiWU+UT9Sixx/wKimvnpkeW0mh6ROAKkIaPokj3xZgxeRhcna/k5X57jJghEr8X+Cgu/Vegf8zCX5ugSuTA==", + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", "dev": true, - "dependencies": { - "@ungap/promise-all-settled": "1.1.2", - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.2", - "debug": "4.3.1", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.1.7", - "growl": "1.10.5", - "he": "1.2.0", - "js-yaml": "4.1.0", - "log-symbols": "4.1.0", - "minimatch": "3.0.4", - "ms": "2.1.3", - "nanoid": "3.1.23", - "serialize-javascript": "6.0.0", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "which": "2.0.2", - "wide-align": "1.1.3", - "workerpool": "6.1.5", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha" - }, + "license": "MIT", "engines": { - "node": ">= 12.0.0" + "node": ">=14" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mochajs" + "url": "https://github.com/sponsors/antonk52" } }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" }, - "node_modules/nanoid": { - "version": "3.1.23", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz", - "integrity": "sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==", + "node_modules/load-tsconfig": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/load-tsconfig/-/load-tsconfig-0.2.5.tgz", + "integrity": "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==", "dev": true, - "bin": { - "nanoid": "bin/nanoid.cjs" - }, + "license": "MIT", "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", "dev": true, - "engines": { - "node": ">=0.10.0" - } + "license": "MIT" }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "dev": true, + "license": "MIT", "dependencies": { - "wrappy": "1" + "@jridgewell/sourcemap-codec": "^1.5.5" } }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "node_modules/mlly": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", + "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", "dev": true, + "license": "MIT", "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "acorn": "^8.16.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.3" } }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", "dev": true, + "license": "MIT", "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" } }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", "dev": true, + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">= 14.16" } }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, "node_modules/picomatch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8.6" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, + "license": "MIT", "engines": { - "node": ">=8.10.0" + "node": ">= 6" } }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", "dev": true, - "engines": { - "node": ">=0.10.0" + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" } }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", "dev": true, "funding": [ { - "type": "github", - "url": "https://github.com/sponsors/feross" + "type": "opencollective", + "url": "https://opencollective.com/postcss/" }, { - "type": "patreon", - "url": "https://www.patreon.com/feross" + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" }, { - "type": "consulting", - "url": "https://feross.org/support" + "type": "github", + "url": "https://github.com/sponsors/ai" } - ] - }, - "node_modules/serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, + ], + "license": "MIT", "dependencies": { - "randombytes": "^2.1.0" + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" } }, - "node_modules/string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", "dependencies": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" + "lilconfig": "^3.1.1" }, "engines": { - "node": ">=4" + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } } }, - "node_modules/strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "node_modules/postcss/node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "dev": true, - "dependencies": { - "ansi-regex": "^3.0.0" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" }, "engines": { - "node": ">=4" + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "node_modules/rollup": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.0.tgz", + "integrity": "sha512-yqjxruMGBQJ2gG4HtjZtAfXArHomazDHoFwFFmZZl0r7Pdo7qCIXKqKHZc8yeoMgzJJ+pO6pEEHa+V7uzWlrAQ==", "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" }, "engines": { - "node": ">=10" + "node": ">=18.0.0", + "npm": ">=8.0.0" }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.0", + "@rollup/rollup-android-arm64": "4.60.0", + "@rollup/rollup-darwin-arm64": "4.60.0", + "@rollup/rollup-darwin-x64": "4.60.0", + "@rollup/rollup-freebsd-arm64": "4.60.0", + "@rollup/rollup-freebsd-x64": "4.60.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.0", + "@rollup/rollup-linux-arm-musleabihf": "4.60.0", + "@rollup/rollup-linux-arm64-gnu": "4.60.0", + "@rollup/rollup-linux-arm64-musl": "4.60.0", + "@rollup/rollup-linux-loong64-gnu": "4.60.0", + "@rollup/rollup-linux-loong64-musl": "4.60.0", + "@rollup/rollup-linux-ppc64-gnu": "4.60.0", + "@rollup/rollup-linux-ppc64-musl": "4.60.0", + "@rollup/rollup-linux-riscv64-gnu": "4.60.0", + "@rollup/rollup-linux-riscv64-musl": "4.60.0", + "@rollup/rollup-linux-s390x-gnu": "4.60.0", + "@rollup/rollup-linux-x64-gnu": "4.60.0", + "@rollup/rollup-linux-x64-musl": "4.60.0", + "@rollup/rollup-openbsd-x64": "4.60.0", + "@rollup/rollup-openharmony-arm64": "4.60.0", + "@rollup/rollup-win32-arm64-msvc": "4.60.0", + "@rollup/rollup-win32-ia32-msvc": "4.60.0", + "@rollup/rollup-win32-x64-gnu": "4.60.0", + "@rollup/rollup-win32-x64-msvc": "4.60.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" } }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", "dev": true, + "license": "MIT" + }, + "node_modules/strip-literal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", + "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", + "dev": true, + "license": "MIT", "dependencies": { - "is-number": "^7.0.0" + "js-tokens": "^9.0.1" }, - "engines": { - "node": ">=8.0" + "funding": { + "url": "https://github.com/sponsors/antfu" } }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", "dev": true, + "license": "MIT", "dependencies": { - "isexe": "^2.0.0" + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" }, "bin": { - "node-which": "bin/node-which" + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" }, "engines": { - "node": ">= 8" + "node": ">=16 || 14 >=14.17" } }, - "node_modules/wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", "dev": true, + "license": "MIT", "dependencies": { - "string-width": "^1.0.2 || 2" + "any-promise": "^1.0.0" } }, - "node_modules/workerpool": { - "version": "6.1.5", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.5.tgz", - "integrity": "sha512-XdKkCK0Zqc6w3iTxLckiuJ81tiD/o5rBE/m+nXpRCB+/Sq4DqkfXZ/x0jW02DG1tGsfUGXbTJyZDP+eu67haSw==", - "dev": true + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "fdir": "^6.5.0", + "picomatch": "^4.0.3" }, "engines": { - "node": ">=10" + "node": ">=12.0.0" }, "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": "^18.0.0 || >=20.0.0" } }, - "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=14.0.0" } }, - "node_modules/wrap-ansi/node_modules/string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "node_modules/tinyspy": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", + "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=14.0.0" } }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", "dev": true, - "dependencies": { - "ansi-regex": "^5.0.0" - }, - "engines": { - "node": ">=8" + "license": "MIT", + "bin": { + "tree-kill": "cli.js" } }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "node_modules/tsup": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/tsup/-/tsup-8.5.1.tgz", + "integrity": "sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==", "dev": true, + "license": "MIT", + "dependencies": { + "bundle-require": "^5.1.0", + "cac": "^6.7.14", + "chokidar": "^4.0.3", + "consola": "^3.4.0", + "debug": "^4.4.0", + "esbuild": "^0.27.0", + "fix-dts-default-cjs-exports": "^1.0.0", + "joycon": "^3.1.1", + "picocolors": "^1.1.1", + "postcss-load-config": "^6.0.1", + "resolve-from": "^5.0.0", + "rollup": "^4.34.8", + "source-map": "^0.7.6", + "sucrase": "^3.35.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.11", + "tree-kill": "^1.2.2" + }, + "bin": { + "tsup": "dist/cli-default.js", + "tsup-node": "dist/cli-node.js" + }, "engines": { - "node": ">=10" + "node": ">=18" + }, + "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 + } } }, - "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "node_modules/tsup/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "dev": true, + "license": "MIT", "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" + "readdirp": "^4.0.1" }, "engines": { - "node": ">=10" + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "node_modules/tsup/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", "dev": true, + "license": "MIT", "engines": { - "node": ">=10" + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, - "node_modules/yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, - "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" }, "engines": { - "node": ">=10" + "node": ">=14.17" } }, - "node_modules/yargs/node_modules/ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "node_modules/ufo": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", + "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", "dev": true, - "engines": { - "node": ">=8" - } + "license": "MIT" }, - "node_modules/yargs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "dev": true, - "engines": { - "node": ">=8" - } + "license": "MIT" }, - "node_modules/yargs/node_modules/string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "node_modules/vite": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, + "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" }, "engines": { - "node": ">=8" + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } } }, - "node_modules/yargs/node_modules/strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "node_modules/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", "dev": true, + "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.0" + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" }, "engines": { - "node": ">=8" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "node_modules/vitest": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", + "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.4", + "@vitest/mocker": "3.2.4", + "@vitest/pretty-format": "^3.2.4", + "@vitest/runner": "3.2.4", + "@vitest/snapshot": "3.2.4", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, "engines": { - "node": ">=10" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.4", + "@vitest/ui": "3.2.4", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" } } } diff --git a/package.json b/package.json index ac30e61..db177ed 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,8 @@ { "name": "classificator", "description": "Naive Bayes classifier with verbose informations for node.js", - "version": "0.5.0", + "version": "1.0.0", + "type": "module", "author": "Wozacosta", "keywords": [ "naive", @@ -14,23 +15,48 @@ "nbayes", "likelihood", "machine learning", - "bayesian" + "bayesian", + "typescript" ], "dependencies": { "decimal.js": "^10.0.0" }, "devDependencies": { - "mocha": "^9.0.2" + "@types/node": "^22.0.0", + "tsup": "^8.5.0", + "typescript": "^5.8.0", + "vitest": "^3.0.0" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" }, - "main": "./lib/classificator", + "main": "./dist/index.cjs", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "import": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "require": { + "types": "./dist/index.d.cts", + "default": "./dist/index.cjs" + } + } + }, + "files": [ + "dist" + ], "repository": { "type": "git", "url": "https://github.com/Wozacosta/classificator.git" }, "scripts": { - "test": "mocha -t 30000 -R spec test/*.js" + "build": "tsup", + "test": "vitest run", + "test:watch": "vitest", + "typecheck": "tsc --noEmit", + "prepublishOnly": "npm run build" } } diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..e7d6b89 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,548 @@ +import Decimal from 'decimal.js' + +// --------------------------------------------------------------------------- +// Types +// --------------------------------------------------------------------------- + +/** Options for configuring the Naive Bayes classifier. */ +export interface NaivebayesOptions { + /** Custom tokenization function. Receives text, must return an array of tokens. */ + tokenizer?: (text: string) => string[] + /** Transform tokens after tokenization (e.g. stopword removal, stemming). */ + tokenPreprocessor?: (tokens: string[]) => string[] + /** Additive (Laplace) smoothing parameter. Default: 1. */ + alpha?: number + /** Whether to use learned prior probabilities. Default: true. */ + fitPrior?: boolean +} + +/** A single category likelihood result. */ +export interface Likelihood { + category: string + logLikelihood: number + logProba: number + proba: number +} + +/** The result of a categorize() call. */ +export interface CategorizeResult { + likelihoods: Likelihood[] + predictedCategory: string | null +} + +/** An influential token result. */ +export interface InfluentialToken { + token: string + probability: number + frequency: number +} + +/** Per-category statistics. */ +export interface CategoryStats { + docCount: number + wordCount: number + vocabularySize: number +} + +/** Result of getCategoryStats(). */ +export interface CategoryStatsResult { + [category: string]: CategoryStats +} + +/** A batch learning item. */ +export interface BatchItem { + text: string + category: string +} + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +/** Keys used to serialize a classifier's state. */ +export const STATE_KEYS = [ + 'categories', + 'docCount', + 'totalDocuments', + 'vocabulary', + 'vocabularySize', + 'wordCount', + 'wordFrequencyCount', + 'options', +] as const + +const DEFAULT_ALPHA = 1 +const DEFAULT_FIT_PRIOR = true + +// --------------------------------------------------------------------------- +// Default tokenizer +// --------------------------------------------------------------------------- + +const defaultTokenizer = (text: string): string[] => { + const rgxPunctuation = /[^(a-zA-ZA-Яa-я0-9_)+\s]/g + const sanitized = text.replace(rgxPunctuation, ' ') + return sanitized.split(/\s+/).filter(token => token.length > 0) +} + +// --------------------------------------------------------------------------- +// Naivebayes class +// --------------------------------------------------------------------------- + +export class Naivebayes { + options: NaivebayesOptions + tokenizer: (text: string) => string[] + tokenPreprocessor: ((tokens: string[]) => string[]) | null + alpha: number + fitPrior: boolean + vocabulary: Record + vocabularySize: number + totalDocuments: number + docCount: Record + wordCount: Record + wordFrequencyCount: Record> + categories: Record + + constructor(options?: NaivebayesOptions) { + this.options = {} + if (typeof options !== 'undefined') { + if (!options || typeof options !== 'object' || Array.isArray(options)) { + throw TypeError( + `NaiveBayes got invalid 'options': '${options}'. Pass in an object.` + ) + } + this.options = options + } + + if (this.options.tokenizer && typeof this.options.tokenizer !== 'function') { + throw TypeError('NaiveBayes: tokenizer must be a function.') + } + if (this.options.tokenPreprocessor && typeof this.options.tokenPreprocessor !== 'function') { + throw TypeError('NaiveBayes: tokenPreprocessor must be a function.') + } + + this.tokenizer = this.options.tokenizer || defaultTokenizer + this.tokenPreprocessor = this.options.tokenPreprocessor || null + this.alpha = this.options.alpha === undefined ? DEFAULT_ALPHA : this.options.alpha + this.fitPrior = this.options.fitPrior === undefined ? DEFAULT_FIT_PRIOR : this.options.fitPrior + + this.vocabulary = {} + this.vocabularySize = 0 + this.totalDocuments = 0 + this.docCount = {} + this.wordCount = {} + this.wordFrequencyCount = {} + this.categories = {} + } + + /** Tokenize text and optionally apply the preprocessor. */ + tokenize(text: string): string[] { + const tokens = this.tokenizer(text) + if (this.tokenPreprocessor) { + return this.tokenPreprocessor(tokens) + } + return tokens + } + + /** Initialize data structure entries for a new category. */ + initializeCategory(categoryName: string): this { + if (!this.categories[categoryName]) { + this.docCount[categoryName] = 0 + this.wordCount[categoryName] = 0 + this.wordFrequencyCount[categoryName] = {} + this.categories[categoryName] = true + } + return this + } + + /** Remove a category and all its associated data. */ + removeCategory(categoryName: string): this { + if (!this.categories[categoryName]) { + return this + } + this.totalDocuments -= this.docCount[categoryName] + + Object.keys(this.wordFrequencyCount[categoryName]).forEach((token) => { + if (this.vocabulary[token] && this.vocabulary[token] > 0) { + this.vocabulary[token]-- + if (this.vocabulary[token] === 0) this.vocabularySize-- + } + }) + + delete this.docCount[categoryName] + delete this.wordCount[categoryName] + delete this.wordFrequencyCount[categoryName] + delete this.categories[categoryName] + + return this + } + + /** Train the classifier: associate `text` with `category`. */ + learn(text: string, category: string): this { + if (typeof text !== 'string') { + throw new TypeError(`NaiveBayes: text must be a string, got ${typeof text}.`) + } + if (typeof category !== 'string') { + throw new TypeError(`NaiveBayes: category must be a string, got ${typeof category}.`) + } + + this.initializeCategory(category) + + this.docCount[category]++ + this.totalDocuments++ + + const tokens = this.tokenize(text) + const freqTable = this.frequencyTable(tokens) + + Object.keys(freqTable).forEach((token) => { + const frequencyInText = freqTable[token] + + if (!this.vocabulary[token] || this.vocabulary[token] === 0) { + this.vocabularySize++ + this.vocabulary[token] = 1 + } else { + this.vocabulary[token]++ + } + + if (!this.wordFrequencyCount[category][token]) { + this.wordFrequencyCount[category][token] = frequencyInText + } else { + this.wordFrequencyCount[category][token] += frequencyInText + } + + this.wordCount[category] += frequencyInText + }) + + return this + } + + /** Untrain the classifier: remove association of `text` with `category`. */ + unlearn(text: string, category: string): this { + if (typeof text !== 'string') { + throw new TypeError(`NaiveBayes: text must be a string, got ${typeof text}.`) + } + if (typeof category !== 'string') { + throw new TypeError(`NaiveBayes: category must be a string, got ${typeof category}.`) + } + if (!this.categories[category]) { + throw new Error(`NaiveBayes: cannot unlearn from non-existent category: '${category}'.`) + } + + this.docCount[category]-- + if (this.docCount[category] === 0) { + delete this.docCount[category] + } + + this.totalDocuments-- + + const tokens = this.tokenize(text) + const freqTable = this.frequencyTable(tokens) + + Object.keys(freqTable).forEach((token) => { + const frequencyInText = freqTable[token] + + if (this.vocabulary[token] && this.vocabulary[token] > 0) { + this.vocabulary[token]-- + if (this.vocabulary[token] === 0) this.vocabularySize-- + } + + if (this.wordFrequencyCount[category] && this.wordFrequencyCount[category][token]) { + this.wordFrequencyCount[category][token] -= frequencyInText + if (this.wordFrequencyCount[category][token] <= 0) { + delete this.wordFrequencyCount[category][token] + } + } + + if (this.wordCount[category] !== undefined) { + this.wordCount[category] -= frequencyInText + if (this.wordCount[category] <= 0) { + delete this.wordCount[category] + delete this.wordFrequencyCount[category] + } + } + }) + + if (!this.docCount[category]) { + delete this.categories[category] + } + + return this + } + + /** Determine what category `text` belongs to. */ + categorize(text: string): CategorizeResult { + if (typeof text !== 'string') { + throw new TypeError(`NaiveBayes: text must be a string, got ${typeof text}.`) + } + + const tokens = this.tokenize(text) + const freqTable = this.frequencyTable(tokens) + const categoryNames = Object.keys(this.categories) + + if (categoryNames.length === 0) { + return { likelihoods: [], predictedCategory: null } + } + + interface InternalLikelihood { + category: string + logLikelihood: Decimal + } + + const likelihoods: InternalLikelihood[] = [] + + categoryNames.forEach((category) => { + let categoryLikelihood: number + if (this.fitPrior) { + categoryLikelihood = this.docCount[category] / this.totalDocuments + } else { + categoryLikelihood = 1 + } + + let logLikelihood = new Decimal(categoryLikelihood).naturalLogarithm() + + Object.keys(freqTable).forEach((token) => { + if (this.vocabulary[token] && this.vocabulary[token] > 0) { + const termFrequencyInText = freqTable[token] + const prob = this.tokenProbability(token, category) + + const logTokenProb = new Decimal(prob).naturalLogarithm() + logLikelihood = logLikelihood.plus(logTokenProb.times(termFrequencyInText)) + } + }) + + likelihoods.push({ category, logLikelihood }) + }) + + // Numerically stable logsumexp: subtract max to prevent overflow/underflow + const logsumexp = (items: InternalLikelihood[]): Decimal => { + if (items.length === 0) return new Decimal(0) + + const maxLog = items.reduce((max, l) => { + return l.logLikelihood.greaterThan(max) ? l.logLikelihood : max + }, items[0].logLikelihood) + + let sum = new Decimal(0) + items.forEach((item) => { + const shifted = item.logLikelihood.minus(maxLog) + sum = sum.plus(Decimal.exp(shifted)) + }) + + return maxLog.plus(sum.naturalLogarithm()) + } + + const logProbX = logsumexp(likelihoods) + + const result: Likelihood[] = likelihoods.map((l) => { + const logProba = l.logLikelihood.minus(logProbX) + const proba = logProba.naturalExponential() + return { + category: l.category, + logLikelihood: l.logLikelihood.toNumber(), + logProba: logProba.toNumber(), + proba: proba.toNumber(), + } + }) + + result.sort((a, b) => b.proba - a.proba) + + return { + likelihoods: result, + predictedCategory: result[0].category, + } + } + + /** Like categorize(), but returns only the top N most likely categories. */ + categorizeTopN(text: string, n: number): CategorizeResult { + const result = this.categorize(text) + if (result.likelihoods.length > n) { + result.likelihoods = result.likelihoods.slice(0, n) + } + return result + } + + /** Categorize with a confidence threshold. Returns null predictedCategory if below threshold. */ + categorizeWithConfidence(text: string, threshold: number): CategorizeResult { + if (typeof threshold !== 'number' || threshold < 0 || threshold > 1) { + throw new TypeError('NaiveBayes: threshold must be a number between 0 and 1.') + } + const result = this.categorize(text) + if (result.predictedCategory === null) return result + + if (result.likelihoods[0].proba < threshold) { + result.predictedCategory = null + } + return result + } + + /** Get the top N most influential tokens for a text's classification. */ + topInfluentialTokens(text: string, n?: number): InfluentialToken[] { + const limit = (n === undefined || n === null) ? 5 : Math.max(0, Math.floor(n)) + const tokens = this.tokenize(text) + const freqTable = this.frequencyTable(tokens) + const result = this.categorize(text) + const topCategory = result.predictedCategory + + if (!topCategory) return [] + + return Object.keys(freqTable) + .filter(token => this.vocabulary[token] && this.vocabulary[token] > 0) + .map(token => ({ + token, + probability: this.tokenProbability(token, topCategory), + frequency: freqTable[token], + })) + .sort((a, b) => b.probability - a.probability) + .slice(0, limit) + } + + /** Calculate probability that a token belongs to a category. */ + tokenProbability(token: string, category: string): number { + const wordFreqCount = this.wordFrequencyCount[category][token] || 0 + const wc = this.wordCount[category] + return (wordFreqCount + this.alpha) / (wc + this.alpha * this.vocabularySize) + } + + /** Build a frequency hashmap from an array of tokens. */ + frequencyTable(tokens: string[]): Record { + const table: Record = Object.create(null) + tokens.forEach((token) => { + if (!table[token]) table[token] = 1 + else table[token]++ + }) + return table + } + + /** Serialize the classifier's state as a JSON string. */ + toJson(): string { + const state: Record = {} + STATE_KEYS.forEach(k => { + state[k] = (this as unknown as Record)[k] + }) + return JSON.stringify(state) + } + + /** Get an array of all category names the classifier has learned. */ + getCategories(): string[] { + return Object.keys(this.categories) + } + + /** Learn from multiple text/category pairs at once. */ + learnBatch(items: BatchItem[]): this { + if (!Array.isArray(items)) { + throw new TypeError('NaiveBayes: learnBatch expects an array of { text, category } objects.') + } + items.forEach(item => { + this.learn(item.text, item.category) + }) + return this + } + + /** Reset the classifier to its initial untrained state, preserving options. */ + reset(): this { + this.vocabulary = {} + this.vocabularySize = 0 + this.totalDocuments = 0 + this.docCount = {} + this.wordCount = {} + this.wordFrequencyCount = {} + this.categories = {} + return this + } + + /** Get statistics about each category's training data. */ + getCategoryStats(): CategoryStatsResult { + const stats: Record = {} + Object.keys(this.categories).forEach(category => { + stats[category] = { + docCount: this.docCount[category] || 0, + wordCount: this.wordCount[category] || 0, + vocabularySize: Object.keys(this.wordFrequencyCount[category] || {}).length, + } + }) + const totalWordCount = Object.keys(this.categories).reduce((sum, cat) => { + return sum + (this.wordCount[cat] || 0) + }, 0) + stats._total = { + docCount: this.totalDocuments, + wordCount: totalWordCount, + vocabularySize: this.vocabularySize, + } + return stats as CategoryStatsResult + } +} + +// --------------------------------------------------------------------------- +// Static: fromJson +// --------------------------------------------------------------------------- + +/** Restore a classifier from its JSON representation. */ +export function fromJson(jsonStrOrObject: string | object, options?: NaivebayesOptions): Naivebayes { + let parameters: Record + + try { + switch (typeof jsonStrOrObject) { + case 'string': + parameters = JSON.parse(jsonStrOrObject) + break + + case 'object': + if (jsonStrOrObject === null) { + throw new Error('') + } + parameters = jsonStrOrObject as Record + break + + default: + throw new Error('') + } + } catch { + throw new Error('NaiveBayes.fromJson expects a valid JSON string or an object.') + } + + const restoredOptions = Object.assign( + {}, + parameters.options as NaivebayesOptions | undefined, + options + ) + + const classifier = new Naivebayes(restoredOptions) + + STATE_KEYS.forEach((k) => { + if (typeof parameters[k] === 'undefined') { + throw new Error( + `NaiveBayes.fromJson: JSON string is missing an expected property: [${k}].` + ) + } + ;(classifier as unknown as Record)[k] = parameters[k] + }) + + // Restore merged options (STATE_KEYS includes 'options' which overwrites + // with saved state, losing runtime-only options like tokenizer/tokenPreprocessor) + classifier.options = restoredOptions + + return classifier +} + +// --------------------------------------------------------------------------- +// Factory function — preserves backward compat for both CJS and ESM +// --------------------------------------------------------------------------- + +export interface ClassifierFactory { + (options?: NaivebayesOptions): Naivebayes + fromJson: typeof fromJson + STATE_KEYS: typeof STATE_KEYS + Naivebayes: typeof Naivebayes +} + +const bayes = function createClassifier(options?: NaivebayesOptions): Naivebayes { + return new Naivebayes(options) +} as ClassifierFactory + +bayes.fromJson = fromJson +bayes.STATE_KEYS = STATE_KEYS +bayes.Naivebayes = Naivebayes + +export default bayes + +// CJS compat: when bundled to CJS, module.exports = bayes +// This is handled by tsup's cjsInterop option which rewrites +// module.exports = module.exports.default when a default export exists diff --git a/test/classificator.js b/test/classificator.js deleted file mode 100644 index 64ba1d9..0000000 --- a/test/classificator.js +++ /dev/null @@ -1,898 +0,0 @@ -var assert = require('assert') - , bayes = require('../lib/classificator') - -describe('bayes() init', function () { - it('valid options (falsey or with an object) do not raise Errors', function () { - var validOptionsCases = [ undefined, {} ]; - - validOptionsCases.forEach(function (validOptions) { - var classifier = bayes(validOptions) - assert.deepEqual(classifier.options, {}) - }) - }) - - it('invalid options (truthy and not object) raise TypeError during init', function () { - var invalidOptionsCases = [ null, 0, 'a', [] ]; - - invalidOptionsCases.forEach(function (invalidOptions) { - assert.throws(function () { bayes(invalidOptions) }, Error) - assert.throws(function () { bayes(invalidOptions) }, TypeError) - }) - }) - - it('throws TypeError when tokenizer is not a function', function () { - assert.throws(function () { bayes({ tokenizer: 'not a function' }) }, TypeError) - assert.throws(function () { bayes({ tokenizer: 42 }) }, TypeError) - }) - - it('throws TypeError when tokenPreprocessor is not a function', function () { - assert.throws(function () { bayes({ tokenPreprocessor: 'bad' }) }, TypeError) - }) -}) - -describe('bayes using custom tokenizer', function () { - it('uses custom tokenization function if one is provided in `options`.', function () { - var splitOnChar = function (text) { - return text.split('') - } - - var classifier = bayes({ tokenizer: splitOnChar }) - - classifier.learn('abcd', 'happy') - - assert.equal(classifier.totalDocuments, 1) - assert.equal(classifier.docCount.happy, 1) - assert.deepEqual(classifier.vocabulary, { a: 1, b: 1, c: 1, d: 1 }) - assert.equal(classifier.vocabularySize, 4) - assert.equal(classifier.wordCount.happy, 4) - assert.equal(classifier.wordFrequencyCount.happy.a, 1) - assert.equal(classifier.wordFrequencyCount.happy.b, 1) - assert.equal(classifier.wordFrequencyCount.happy.c, 1) - assert.equal(classifier.wordFrequencyCount.happy.d, 1) - assert.deepStrictEqual(classifier.categories, { happy: true }) - }) -}) - -describe('bayes using tokenPreprocessor', function () { - it('applies tokenPreprocessor after tokenizer', function () { - var stopwords = new Set(['the', 'a', 'is', 'in']) - var classifier = bayes({ - tokenPreprocessor: function (tokens) { - return tokens - .map(function (t) { return t.toLowerCase() }) - .filter(function (t) { return !stopwords.has(t) }) - } - }) - - classifier.learn('The cat is in a hat', 'animals') - - // stopwords should be removed - assert.equal(classifier.wordFrequencyCount.animals['the'], undefined) - assert.equal(classifier.wordFrequencyCount.animals['a'], undefined) - assert.equal(classifier.wordFrequencyCount.animals['is'], undefined) - assert.equal(classifier.wordFrequencyCount.animals['in'], undefined) - - // content words should remain (lowercased) - assert.equal(classifier.wordFrequencyCount.animals['cat'], 1) - assert.equal(classifier.wordFrequencyCount.animals['hat'], 1) - }) - - it('works with stemming-style preprocessor', function () { - var classifier = bayes({ - tokenPreprocessor: function (tokens) { - return tokens.map(function (t) { - // crude stemming: strip trailing 's', 'ing', 'ed' - return t.replace(/(ing|ed|s)$/i, '').toLowerCase() - }) - } - }) - - classifier.learn('running dogs played', 'active') - classifier.learn('sleeping cats rested', 'passive') - - var result = classifier.categorize('dogs playing') - assert.equal(result.predictedCategory, 'active') - }) - - it('preprocessor is preserved through fromJson with options', function () { - var preprocessor = function (tokens) { - return tokens.map(function (t) { return t.toLowerCase() }) - } - - var classifier = bayes({ tokenPreprocessor: preprocessor }) - classifier.learn('HELLO', 'greetings') - - var revived = bayes.fromJson(classifier.toJson(), { tokenPreprocessor: preprocessor }) - var result = revived.categorize('HELLO') - assert.equal(result.predictedCategory, 'greetings') - }) - - it('classifier.options preserves runtime options after fromJson', function () { - var preprocessor = function (tokens) { - return tokens.map(function (t) { return t.toLowerCase() }) - } - var tokenizer = function (text) { return text.split(' ') } - - var classifier = bayes({ tokenPreprocessor: preprocessor, tokenizer: tokenizer }) - classifier.learn('HELLO WORLD', 'greetings') - - var revived = bayes.fromJson(classifier.toJson(), { - tokenPreprocessor: preprocessor, - tokenizer: tokenizer - }) - - assert.strictEqual(revived.options.tokenPreprocessor, preprocessor) - assert.strictEqual(revived.options.tokenizer, tokenizer) - assert.strictEqual(revived.tokenPreprocessor, preprocessor) - assert.strictEqual(revived.tokenizer, tokenizer) - }) -}) - -describe('bayes serializing/deserializing its state', function () { - it('serializes/deserializes its state as JSON correctly.', function () { - var classifier = bayes() - - classifier.learn('Fun times were had by all', 'positive') - classifier.learn('sad dark rainy day in the cave', 'negative') - - var jsonRepr = classifier.toJson() - var state = JSON.parse(jsonRepr) - - bayes.STATE_KEYS.forEach(function (k) { - assert.deepEqual(state[k], classifier[k]) - }) - - var revivedClassifier = bayes.fromJson(jsonRepr) - - bayes.STATE_KEYS.forEach(function (k) { - assert.deepEqual(revivedClassifier[k], classifier[k]) - }) - }) -}) - -describe('bayes using custom tokenizer with fromJson', function () { - it('accepts a custom tokenizer passed as an option to fromJson', function () { - var splitOnChar = function (text) { - return text.split('') - } - - var classifier = bayes({ tokenizer: splitOnChar }) - - classifier.learn('abcd', 'happy') - classifier.learn('efgh', 'sad') - - var jsonRepr = classifier.toJson() - var revivedClassifier = bayes.fromJson(jsonRepr, { tokenizer: splitOnChar }) - - var result = revivedClassifier.categorize('abcd') - assert.equal(result.predictedCategory, 'happy') - }) -}) - -describe('bayes .fromJson() edge cases', function () { - it('throws on null input', function () { - assert.throws(function () { bayes.fromJson(null) }, Error) - }) - - it('throws on numeric input', function () { - assert.throws(function () { bayes.fromJson(42) }, Error) - }) - - it('throws on invalid JSON string', function () { - assert.throws(function () { bayes.fromJson('not valid json') }, Error) - }) - - it('throws when JSON is missing required state keys', function () { - assert.throws(function () { bayes.fromJson('{"options":{}}') }, Error) - }) - - it('preserves options through serialization round-trip', function () { - var classifier = bayes({ alpha: 2, fitPrior: false }) - classifier.learn('hello world', 'greetings') - - var revived = bayes.fromJson(classifier.toJson()) - assert.equal(revived.alpha, 2) - assert.equal(revived.fitPrior, false) - }) -}) - -describe('bayes .learn() correctness', function () { - it('categorizes correctly for `positive` and `negative` categories', function () { - let classifier = bayes(); - - classifier.learn('amazing, awesome movie!! Yeah!!', 'positive') - classifier.learn('Sweet, this is incredibly, amazing, perfect, great!!', 'positive') - classifier.learn('terrible, shitty thing. Damn. Sucks!!', 'negative') - classifier.learn('I dont really know what to make of this.', 'neutral') - - assert.deepEqual(classifier.categorize('awesome, cool, amazing!! Yay.').predictedCategory, 'positive') - }) - - it('categorizes correctly for `chinese` and `japanese` categories', function () { - var classifier = bayes() - - classifier.learn('Chinese Beijing Chinese', 'chinese') - classifier.learn('Chinese Chinese Shanghai', 'chinese') - classifier.learn('Chinese Macao', 'chinese') - classifier.learn('Tokyo Japan Chinese', 'japanese') - - var chineseFrequencyCount = classifier.wordFrequencyCount.chinese - - assert.equal(chineseFrequencyCount['Chinese'], 5) - assert.equal(chineseFrequencyCount['Beijing'], 1) - assert.equal(chineseFrequencyCount['Shanghai'], 1) - assert.equal(chineseFrequencyCount['Macao'], 1) - - var japaneseFrequencyCount = classifier.wordFrequencyCount.japanese - - assert.equal(japaneseFrequencyCount['Tokyo'], 1) - assert.equal(japaneseFrequencyCount['Japan'], 1) - assert.equal(japaneseFrequencyCount['Chinese'], 1) - - assert.deepEqual(classifier.categorize('Chinese Chinese Chinese Tokyo Japan').predictedCategory,'chinese') - }) - - it('correctly tokenizes cyrlic characters', function () { - var classifier = bayes() - - classifier.learn('Надежда за', 'a') - classifier.learn('Надежда за обич еп.36 Тест', 'b') - classifier.learn('Надежда за обич еп.36 Тест', 'b') - - var aFreqCount = classifier.wordFrequencyCount.a - assert.equal(aFreqCount['Надежда'], 1) - assert.equal(aFreqCount['за'], 1) - - var bFreqCount = classifier.wordFrequencyCount.b - assert.equal(bFreqCount['Надежда'], 2) - assert.equal(bFreqCount['за'], 2) - assert.equal(bFreqCount['обич'], 2) - assert.equal(bFreqCount['еп'], 2) - assert.equal(bFreqCount['36'], 2) - assert.equal(bFreqCount['Тест'], 2) - }) - - it('correctly computes probabilities without prior', function () { - var classifier = bayes({ fitPrior: false}) - - classifier.learn('aa', '1') - classifier.learn('aa', '1') - classifier.learn('aa', '1') - classifier.learn('bb', '2') - - assert.equal(classifier.categorize('cc').likelihoods[0].proba, 0.5) - assert.equal(Number(classifier.categorize('bb').likelihoods[0].proba).toFixed(6), Number(0.76923077).toFixed(6)) - assert.equal(Number(classifier.categorize('aa').likelihoods[0].proba).toFixed(6), Number(0.70588235).toFixed(6)) - }) - - it('correctly computes probabilities with prior', function () { - var classifier = bayes() - - classifier.learn('aa', '1') - classifier.learn('aa', '1') - classifier.learn('aa', '1') - classifier.learn('bb', '2') - - assert.equal(classifier.categorize('cc').likelihoods[0].proba, 0.75) - assert.equal(Number(classifier.categorize('bb').likelihoods[0].proba).toFixed(6), Number(0.52631579).toFixed(6)) - assert.equal(Number(classifier.categorize('aa').likelihoods[0].proba).toFixed(6), Number(0.87804878).toFixed(6)) - }) - - it('throws TypeError when text is not a string', function () { - var classifier = bayes() - assert.throws(function () { classifier.learn(123, 'cat') }, TypeError) - assert.throws(function () { classifier.learn(null, 'cat') }, TypeError) - }) - - it('throws TypeError when category is not a string', function () { - var classifier = bayes() - assert.throws(function () { classifier.learn('hello', 123) }, TypeError) - assert.throws(function () { classifier.learn('hello', null) }, TypeError) - }) -}) - -describe('bayes .unlearn() correctness', function () { - it('reverses the effect of a single learn call', function () { - var classifier = bayes() - - classifier.learn('fun times', 'positive') - classifier.learn('bad times', 'negative') - classifier.learn('great day', 'positive') - - var docsBefore = classifier.totalDocuments - classifier.unlearn('fun times', 'positive') - - assert.equal(classifier.totalDocuments, docsBefore - 1) - }) - - it('throws when unlearning from a non-existent category', function () { - var classifier = bayes() - classifier.learn('hello', 'greetings') - - assert.throws(function () { - classifier.unlearn('hello', 'nonexistent') - }, Error) - }) - - it('removes category from categories when last doc is unlearned', function () { - var classifier = bayes() - - classifier.learn('hello', 'greetings') - assert.ok(classifier.categories['greetings']) - - classifier.unlearn('hello', 'greetings') - assert.equal(classifier.categories['greetings'], undefined) - }) - - it('classifier still works correctly after unlearn', function () { - var classifier = bayes() - - classifier.learn('amazing great', 'positive') - classifier.learn('terrible awful', 'negative') - classifier.learn('bad horrible', 'negative') - - classifier.unlearn('bad horrible', 'negative') - - var result = classifier.categorize('terrible') - assert.equal(result.predictedCategory, 'negative') - }) - - it('returns this for method chaining', function () { - var classifier = bayes() - classifier.learn('hello', 'greetings') - var result = classifier.unlearn('hello', 'greetings') - assert.strictEqual(result, classifier) - }) - - it('throws TypeError when text is not a string', function () { - var classifier = bayes() - classifier.learn('hello', 'greetings') - assert.throws(function () { classifier.unlearn(123, 'greetings') }, TypeError) - }) - - it('does not leave negative wordCount', function () { - var classifier = bayes() - classifier.learn('hello', 'greetings') - classifier.unlearn('hello', 'greetings') - - assert.equal(classifier.wordCount['greetings'], undefined) - }) -}) - -describe('bayes .removeCategory()', function () { - it('removes a category and its associated data', function () { - var classifier = bayes() - - classifier.learn('hello world', 'greetings') - classifier.learn('bad stuff', 'negative') - - classifier.removeCategory('greetings') - - assert.equal(classifier.categories['greetings'], undefined) - assert.equal(classifier.docCount['greetings'], undefined) - assert.equal(classifier.wordCount['greetings'], undefined) - assert.equal(classifier.wordFrequencyCount['greetings'], undefined) - }) - - it('returns this for chaining', function () { - var classifier = bayes() - classifier.learn('hello', 'greetings') - var result = classifier.removeCategory('greetings') - assert.strictEqual(result, classifier) - }) - - it('returns this when removing non-existent category (no-op)', function () { - var classifier = bayes() - var result = classifier.removeCategory('nonexistent') - assert.strictEqual(result, classifier) - }) - - it('classifier still categorizes correctly after removing a category', function () { - var classifier = bayes() - - classifier.learn('amazing great', 'positive') - classifier.learn('terrible bad', 'negative') - classifier.learn('meh ok', 'neutral') - - classifier.removeCategory('neutral') - - var result = classifier.categorize('amazing') - assert.equal(result.predictedCategory, 'positive') - assert.equal(result.likelihoods.length, 2) - }) - - it('updates vocabulary and vocabularySize correctly', function () { - var classifier = bayes() - - classifier.learn('unique', 'only') - var sizeBefore = classifier.vocabularySize - - classifier.removeCategory('only') - assert.ok(classifier.vocabularySize < sizeBefore) - }) - - it('does not produce negative vocabulary counts', function () { - var classifier = bayes() - - classifier.learn('shared word', 'a') - classifier.learn('shared word', 'b') - - classifier.removeCategory('a') - - // 'shared' and 'word' should still have count >= 0 - Object.keys(classifier.vocabulary).forEach(function (token) { - assert.ok(classifier.vocabulary[token] >= 0, - 'vocabulary[' + token + '] should be >= 0, got ' + classifier.vocabulary[token]) - }) - }) -}) - -describe('bayes .categorize() return structure', function () { - it('returns an object with predictedCategory and likelihoods', function () { - var classifier = bayes() - classifier.learn('hello', 'greetings') - - var result = classifier.categorize('hello') - assert.ok(result.hasOwnProperty('predictedCategory')) - assert.ok(result.hasOwnProperty('likelihoods')) - assert.ok(Array.isArray(result.likelihoods)) - }) - - it('likelihoods contain category, logLikelihood, logProba, proba', function () { - var classifier = bayes() - classifier.learn('hello', 'greetings') - - var result = classifier.categorize('hello') - var likelihood = result.likelihoods[0] - - assert.ok(likelihood.hasOwnProperty('category')) - assert.ok(likelihood.hasOwnProperty('logLikelihood')) - assert.ok(likelihood.hasOwnProperty('logProba')) - assert.ok(likelihood.hasOwnProperty('proba')) - }) - - it('likelihoods are sorted by proba descending', function () { - var classifier = bayes() - classifier.learn('aa', 'a') - classifier.learn('bb', 'b') - classifier.learn('cc', 'c') - - var result = classifier.categorize('aa') - for (var i = 1; i < result.likelihoods.length; i++) { - assert.ok(result.likelihoods[i - 1].proba >= result.likelihoods[i].proba) - } - }) - - it('probabilities sum to approximately 1.0', function () { - var classifier = bayes() - classifier.learn('happy fun', 'positive') - classifier.learn('sad bad', 'negative') - - var result = classifier.categorize('happy') - var sum = result.likelihoods.reduce(function (acc, l) { return acc + l.proba }, 0) - assert.ok(Math.abs(sum - 1.0) < 0.001) - }) - - it('returns predictedCategory null for empty classifier', function () { - var classifier = bayes() - var result = classifier.categorize('hello') - - assert.equal(result.predictedCategory, null) - assert.deepEqual(result.likelihoods, []) - }) - - it('throws TypeError when text is not a string', function () { - var classifier = bayes() - assert.throws(function () { classifier.categorize(123) }, TypeError) - assert.throws(function () { classifier.categorize(null) }, TypeError) - }) -}) - -describe('bayes edge cases', function () { - it('handles empty string input to categorize()', function () { - var classifier = bayes() - classifier.learn('hello world', 'greetings') - - var result = classifier.categorize('') - assert.ok(result.hasOwnProperty('predictedCategory')) - }) - - it('handles text with only punctuation', function () { - var classifier = bayes() - classifier.learn('hello', 'greetings') - - var result = classifier.categorize('!@#$%') - assert.ok(result.hasOwnProperty('predictedCategory')) - }) - - it('handles unknown tokens gracefully', function () { - var classifier = bayes() - classifier.learn('hello world', 'greetings') - - var result = classifier.categorize('xyzzy foobar') - assert.ok(result.hasOwnProperty('predictedCategory')) - }) -}) - -describe('bayes alpha parameter', function () { - it('uses default alpha of 1 when not specified', function () { - var classifier = bayes() - assert.equal(classifier.alpha, 1) - }) - - it('accepts alpha: 0 without overriding to default', function () { - var classifier = bayes({ alpha: 0 }) - assert.strictEqual(classifier.alpha, 0) - }) - - it('custom alpha affects token probability calculation', function () { - var classifier1 = bayes({ alpha: 1 }) - var classifier2 = bayes({ alpha: 10 }) - - classifier1.learn('hello world', 'greetings') - classifier1.learn('goodbye world', 'farewells') - classifier2.learn('hello world', 'greetings') - classifier2.learn('goodbye world', 'farewells') - - var prob1 = classifier1.tokenProbability('hello', 'greetings') - var prob2 = classifier2.tokenProbability('hello', 'greetings') - - assert.notEqual(prob1, prob2) - }) - - it('alpha: 0 categorization works but unseen tokens zero-out a category', function () { - var classifier = bayes({ alpha: 0 }) - - classifier.learn('happy fun', 'positive') - classifier.learn('sad bad', 'negative') - - // 'happy' was only seen in positive, so negative gets zero probability for it - var result = classifier.categorize('happy') - assert.equal(result.predictedCategory, 'positive') - - // result should not contain NaN - result.likelihoods.forEach(function (l) { - assert.ok(!isNaN(l.proba), 'proba should not be NaN') - }) - }) -}) - -describe('bayes method chaining', function () { - it('learn() returns this', function () { - var classifier = bayes() - assert.strictEqual(classifier.learn('hello', 'greetings'), classifier) - }) - - it('initializeCategory() returns this', function () { - var classifier = bayes() - assert.strictEqual(classifier.initializeCategory('test'), classifier) - }) - - it('removeCategory() returns this', function () { - var classifier = bayes() - assert.strictEqual(classifier.removeCategory('test'), classifier) - }) - - it('supports fluent chain: learn().learn().categorize()', function () { - var result = bayes() - .learn('happy fun', 'positive') - .learn('sad bad', 'negative') - .categorize('happy') - - assert.equal(result.predictedCategory, 'positive') - }) -}) - -describe('bayes .getCategories()', function () { - it('returns empty array for new classifier', function () { - var classifier = bayes() - assert.deepEqual(classifier.getCategories(), []) - }) - - it('returns array of learned category names', function () { - var classifier = bayes() - classifier.learn('hello', 'greetings') - classifier.learn('bye', 'farewells') - - var categories = classifier.getCategories() - assert.ok(categories.indexOf('greetings') !== -1) - assert.ok(categories.indexOf('farewells') !== -1) - assert.equal(categories.length, 2) - }) - - it('reflects category removal', function () { - var classifier = bayes() - classifier.learn('hello', 'greetings') - classifier.learn('bye', 'farewells') - classifier.removeCategory('greetings') - - var categories = classifier.getCategories() - assert.ok(categories.indexOf('greetings') === -1) - assert.equal(categories.length, 1) - }) -}) - -describe('bayes .categorizeTopN()', function () { - it('returns only top N categories', function () { - var classifier = bayes() - classifier.learn('aa', 'a') - classifier.learn('bb', 'b') - classifier.learn('cc', 'c') - - var result = classifier.categorizeTopN('aa', 2) - assert.equal(result.likelihoods.length, 2) - }) - - it('returns all categories if N >= total categories', function () { - var classifier = bayes() - classifier.learn('aa', 'a') - classifier.learn('bb', 'b') - - var result = classifier.categorizeTopN('aa', 10) - assert.equal(result.likelihoods.length, 2) - }) - - it('predictedCategory is the most likely', function () { - var classifier = bayes() - classifier.learn('happy fun great', 'positive') - classifier.learn('sad bad terrible', 'negative') - classifier.learn('ok meh whatever', 'neutral') - - var result = classifier.categorizeTopN('happy fun', 1) - assert.equal(result.predictedCategory, 'positive') - assert.equal(result.likelihoods.length, 1) - }) -}) - -describe('bayes .categorizeWithConfidence()', function () { - it('returns predictedCategory when above threshold', function () { - var classifier = bayes() - classifier.learn('happy fun great amazing', 'positive') - classifier.learn('sad bad terrible awful', 'negative') - - var result = classifier.categorizeWithConfidence('happy fun great', 0.5) - assert.equal(result.predictedCategory, 'positive') - }) - - it('returns null predictedCategory when below threshold', function () { - var classifier = bayes() - classifier.learn('aa', 'a') - classifier.learn('bb', 'b') - - // with a very high threshold, prediction should be null - var result = classifier.categorizeWithConfidence('cc', 0.99) - assert.equal(result.predictedCategory, null) - }) - - it('returns null for empty classifier', function () { - var classifier = bayes() - var result = classifier.categorizeWithConfidence('hello', 0.5) - assert.equal(result.predictedCategory, null) - }) - - it('still returns full likelihoods array', function () { - var classifier = bayes() - classifier.learn('aa', 'a') - classifier.learn('bb', 'b') - - var result = classifier.categorizeWithConfidence('cc', 0.99) - assert.ok(Array.isArray(result.likelihoods)) - assert.ok(result.likelihoods.length > 0) - }) - - it('throws TypeError for invalid threshold', function () { - var classifier = bayes() - classifier.learn('hello', 'greetings') - - assert.throws(function () { classifier.categorizeWithConfidence('hello', -1) }, TypeError) - assert.throws(function () { classifier.categorizeWithConfidence('hello', 2) }, TypeError) - assert.throws(function () { classifier.categorizeWithConfidence('hello', 'bad') }, TypeError) - }) -}) - -describe('bayes .topInfluentialTokens()', function () { - it('returns top tokens for a classification', function () { - var classifier = bayes() - classifier.learn('happy fun great joy', 'positive') - classifier.learn('sad bad terrible gloom', 'negative') - - var tokens = classifier.topInfluentialTokens('happy fun great', 3) - assert.ok(Array.isArray(tokens)) - assert.ok(tokens.length <= 3) - assert.ok(tokens.length > 0) - - // each token should have the right shape - tokens.forEach(function (t) { - assert.ok(t.hasOwnProperty('token')) - assert.ok(t.hasOwnProperty('probability')) - assert.ok(t.hasOwnProperty('frequency')) - }) - }) - - it('returns empty array for empty classifier', function () { - var classifier = bayes() - var tokens = classifier.topInfluentialTokens('hello') - assert.deepEqual(tokens, []) - }) - - it('tokens are sorted by probability descending', function () { - var classifier = bayes() - classifier.learn('apple banana cherry', 'fruit') - classifier.learn('dog cat bird', 'animal') - - var tokens = classifier.topInfluentialTokens('apple banana cherry', 5) - for (var i = 1; i < tokens.length; i++) { - assert.ok(tokens[i - 1].probability >= tokens[i].probability) - } - }) - - it('defaults to 5 tokens', function () { - var classifier = bayes() - classifier.learn('a b c d e f g h', 'letters') - classifier.learn('1 2 3', 'numbers') - - var tokens = classifier.topInfluentialTokens('a b c d e f g h') - assert.ok(tokens.length <= 5) - }) - - it('returns empty array when n is 0', function () { - var classifier = bayes() - classifier.learn('hello world', 'greetings') - - var tokens = classifier.topInfluentialTokens('hello world', 0) - assert.deepEqual(tokens, []) - }) - - it('handles negative n by returning empty array', function () { - var classifier = bayes() - classifier.learn('hello world', 'greetings') - - var tokens = classifier.topInfluentialTokens('hello world', -3) - assert.deepEqual(tokens, []) - }) -}) - -describe('bayes .learnBatch()', function () { - it('learns multiple items at once', function () { - var classifier = bayes() - classifier.learnBatch([ - { text: 'happy fun', category: 'positive' }, - { text: 'sad bad', category: 'negative' } - ]) - - assert.equal(classifier.totalDocuments, 2) - assert.ok(classifier.categories['positive']) - assert.ok(classifier.categories['negative']) - }) - - it('produces same result as individual learn calls', function () { - var classifier1 = bayes() - classifier1.learn('happy fun', 'positive') - classifier1.learn('sad bad', 'negative') - - var classifier2 = bayes() - classifier2.learnBatch([ - { text: 'happy fun', category: 'positive' }, - { text: 'sad bad', category: 'negative' } - ]) - - assert.deepEqual(classifier1.vocabulary, classifier2.vocabulary) - assert.equal(classifier1.totalDocuments, classifier2.totalDocuments) - assert.deepEqual(classifier1.docCount, classifier2.docCount) - }) - - it('throws TypeError on non-array input', function () { - var classifier = bayes() - assert.throws(function () { classifier.learnBatch('not an array') }, TypeError) - assert.throws(function () { classifier.learnBatch(42) }, TypeError) - }) - - it('returns this for chaining', function () { - var classifier = bayes() - var result = classifier.learnBatch([{ text: 'hello', category: 'greetings' }]) - assert.strictEqual(result, classifier) - }) -}) - -describe('bayes .reset()', function () { - it('clears all learned data', function () { - var classifier = bayes() - classifier.learn('hello world', 'greetings') - classifier.learn('goodbye world', 'farewells') - - classifier.reset() - - assert.equal(classifier.totalDocuments, 0) - assert.equal(classifier.vocabularySize, 0) - assert.deepEqual(classifier.categories, {}) - assert.deepEqual(classifier.vocabulary, {}) - assert.deepEqual(classifier.docCount, {}) - assert.deepEqual(classifier.wordCount, {}) - assert.deepEqual(classifier.wordFrequencyCount, {}) - }) - - it('preserves options (tokenizer, alpha, fitPrior)', function () { - var customTokenizer = function (text) { return text.split('') } - var classifier = bayes({ tokenizer: customTokenizer, alpha: 2, fitPrior: false }) - classifier.learn('abc', 'letters') - - classifier.reset() - - assert.strictEqual(classifier.tokenizer, customTokenizer) - assert.equal(classifier.alpha, 2) - assert.equal(classifier.fitPrior, false) - }) - - it('classifier can be retrained after reset', function () { - var classifier = bayes() - classifier.learn('hello', 'greetings') - classifier.reset() - classifier.learn('goodbye', 'farewells') - - assert.equal(classifier.totalDocuments, 1) - assert.deepEqual(classifier.getCategories(), ['farewells']) - }) - - it('returns this for chaining', function () { - var classifier = bayes() - assert.strictEqual(classifier.reset(), classifier) - }) -}) - -describe('bayes .getCategoryStats()', function () { - it('returns correct doc and word counts per category', function () { - var classifier = bayes() - classifier.learn('hello world', 'greetings') - classifier.learn('goodbye world', 'farewells') - classifier.learn('hi there', 'greetings') - - var stats = classifier.getCategoryStats() - - assert.equal(stats.greetings.docCount, 2) - assert.equal(stats.farewells.docCount, 1) - assert.ok(stats.greetings.wordCount > 0) - assert.ok(stats.greetings.vocabularySize > 0) - }) - - it('includes _total aggregate stats with wordCount', function () { - var classifier = bayes() - classifier.learn('hello world', 'greetings') - classifier.learn('bye now', 'farewells') - - var stats = classifier.getCategoryStats() - - assert.equal(stats._total.docCount, 2) - assert.ok(stats._total.vocabularySize > 0) - assert.equal(stats._total.wordCount, stats.greetings.wordCount + stats.farewells.wordCount) - }) -}) - -describe('bayes numerical stability (logsumexp)', function () { - it('probabilities still sum to ~1.0 with many categories', function () { - var classifier = bayes() - for (var i = 0; i < 20; i++) { - classifier.learn('word' + i + ' common shared text', 'cat' + i) - } - - var result = classifier.categorize('word0 common shared') - var sum = result.likelihoods.reduce(function (acc, l) { return acc + l.proba }, 0) - assert.ok(Math.abs(sum - 1.0) < 0.01, 'probabilities should sum to ~1.0, got ' + sum) - }) - - it('handles long documents without NaN', function () { - var classifier = bayes() - classifier.learn('good great amazing wonderful fantastic', 'positive') - classifier.learn('bad terrible awful horrible dreadful', 'negative') - - // create a long document - var longText = '' - for (var i = 0; i < 100; i++) { - longText += 'good great amazing ' - } - - var result = classifier.categorize(longText) - assert.ok(!isNaN(result.likelihoods[0].proba), 'proba should not be NaN') - assert.ok(result.likelihoods[0].proba > 0, 'proba should be positive') - assert.equal(result.predictedCategory, 'positive') - }) -}) diff --git a/test/classificator.test.ts b/test/classificator.test.ts new file mode 100644 index 0000000..e6e956e --- /dev/null +++ b/test/classificator.test.ts @@ -0,0 +1,897 @@ +import { describe, it, expect } from 'vitest' +import bayes from '../src/index' + +describe('bayes() init', () => { + it('valid options (falsey or with an object) do not raise Errors', () => { + const validOptionsCases = [ undefined, {} ]; + + validOptionsCases.forEach((validOptions) => { + const classifier = bayes(validOptions) + expect(classifier.options).toEqual({}) + }) + }) + + it('invalid options (truthy and not object) raise TypeError during init', () => { + const invalidOptionsCases = [ null, 0, 'a', [] ]; + + invalidOptionsCases.forEach((invalidOptions) => { + expect(() => { bayes(invalidOptions) }).toThrow(Error) + expect(() => { bayes(invalidOptions) }).toThrow(TypeError) + }) + }) + + it('throws TypeError when tokenizer is not a function', () => { + expect(() => { bayes({ tokenizer: 'not a function' }) }).toThrow(TypeError) + expect(() => { bayes({ tokenizer: 42 }) }).toThrow(TypeError) + }) + + it('throws TypeError when tokenPreprocessor is not a function', () => { + expect(() => { bayes({ tokenPreprocessor: 'bad' }) }).toThrow(TypeError) + }) +}) + +describe('bayes using custom tokenizer', () => { + it('uses custom tokenization function if one is provided in `options`.', () => { + const splitOnChar = (text) => { + return text.split('') + } + + const classifier = bayes({ tokenizer: splitOnChar }) + + classifier.learn('abcd', 'happy') + + expect(classifier.totalDocuments).toBe(1) + expect(classifier.docCount.happy).toBe(1) + expect(classifier.vocabulary).toEqual({ a: 1, b: 1, c: 1, d: 1 }) + expect(classifier.vocabularySize).toBe(4) + expect(classifier.wordCount.happy).toBe(4) + expect(classifier.wordFrequencyCount.happy.a).toBe(1) + expect(classifier.wordFrequencyCount.happy.b).toBe(1) + expect(classifier.wordFrequencyCount.happy.c).toBe(1) + expect(classifier.wordFrequencyCount.happy.d).toBe(1) + expect(classifier.categories).toStrictEqual({ happy: true }) + }) +}) + +describe('bayes using tokenPreprocessor', () => { + it('applies tokenPreprocessor after tokenizer', () => { + const stopwords = new Set(['the', 'a', 'is', 'in']) + const classifier = bayes({ + tokenPreprocessor: (tokens) => { + return tokens + .map((t) => { return t.toLowerCase() }) + .filter((t) => { return !stopwords.has(t) }) + } + }) + + classifier.learn('The cat is in a hat', 'animals') + + // stopwords should be removed + expect(classifier.wordFrequencyCount.animals['the']).toBe(undefined) + expect(classifier.wordFrequencyCount.animals['a']).toBe(undefined) + expect(classifier.wordFrequencyCount.animals['is']).toBe(undefined) + expect(classifier.wordFrequencyCount.animals['in']).toBe(undefined) + + // content words should remain (lowercased) + expect(classifier.wordFrequencyCount.animals['cat']).toBe(1) + expect(classifier.wordFrequencyCount.animals['hat']).toBe(1) + }) + + it('works with stemming-style preprocessor', () => { + const classifier = bayes({ + tokenPreprocessor: (tokens) => { + return tokens.map((t) => { + // crude stemming: strip trailing 's', 'ing', 'ed' + return t.replace(/(ing|ed|s)$/i, '').toLowerCase() + }) + } + }) + + classifier.learn('running dogs played', 'active') + classifier.learn('sleeping cats rested', 'passive') + + const result = classifier.categorize('dogs playing') + expect(result.predictedCategory).toBe('active') + }) + + it('preprocessor is preserved through fromJson with options', () => { + const preprocessor = (tokens) => { + return tokens.map((t) => { return t.toLowerCase() }) + } + + const classifier = bayes({ tokenPreprocessor: preprocessor }) + classifier.learn('HELLO', 'greetings') + + const revived = bayes.fromJson(classifier.toJson(), { tokenPreprocessor: preprocessor }) + const result = revived.categorize('HELLO') + expect(result.predictedCategory).toBe('greetings') + }) + + it('classifier.options preserves runtime options after fromJson', () => { + const preprocessor = (tokens) => { + return tokens.map((t) => { return t.toLowerCase() }) + } + const tokenizer = (text) => { return text.split(' ') } + + const classifier = bayes({ tokenPreprocessor: preprocessor, tokenizer: tokenizer }) + classifier.learn('HELLO WORLD', 'greetings') + + const revived = bayes.fromJson(classifier.toJson(), { + tokenPreprocessor: preprocessor, + tokenizer: tokenizer + }) + + expect(revived.options.tokenPreprocessor).toBe(preprocessor) + expect(revived.options.tokenizer).toBe(tokenizer) + expect(revived.tokenPreprocessor).toBe(preprocessor) + expect(revived.tokenizer).toBe(tokenizer) + }) +}) + +describe('bayes serializing/deserializing its state', () => { + it('serializes/deserializes its state as JSON correctly.', () => { + const classifier = bayes() + + classifier.learn('Fun times were had by all', 'positive') + classifier.learn('sad dark rainy day in the cave', 'negative') + + const jsonRepr = classifier.toJson() + const state = JSON.parse(jsonRepr) + + bayes.STATE_KEYS.forEach((k) => { + expect(state[k]).toEqual(classifier[k]) + }) + + const revivedClassifier = bayes.fromJson(jsonRepr) + + bayes.STATE_KEYS.forEach((k) => { + expect(revivedClassifier[k]).toEqual(classifier[k]) + }) + }) +}) + +describe('bayes using custom tokenizer with fromJson', () => { + it('accepts a custom tokenizer passed as an option to fromJson', () => { + const splitOnChar = (text) => { + return text.split('') + } + + const classifier = bayes({ tokenizer: splitOnChar }) + + classifier.learn('abcd', 'happy') + classifier.learn('efgh', 'sad') + + const jsonRepr = classifier.toJson() + const revivedClassifier = bayes.fromJson(jsonRepr, { tokenizer: splitOnChar }) + + const result = revivedClassifier.categorize('abcd') + expect(result.predictedCategory).toBe('happy') + }) +}) + +describe('bayes .fromJson() edge cases', () => { + it('throws on null input', () => { + expect(() => { bayes.fromJson(null) }).toThrow(Error) + }) + + it('throws on numeric input', () => { + expect(() => { bayes.fromJson(42) }).toThrow(Error) + }) + + it('throws on invalid JSON string', () => { + expect(() => { bayes.fromJson('not valid json') }).toThrow(Error) + }) + + it('throws when JSON is missing required state keys', () => { + expect(() => { bayes.fromJson('{"options":{}}') }).toThrow(Error) + }) + + it('preserves options through serialization round-trip', () => { + const classifier = bayes({ alpha: 2, fitPrior: false }) + classifier.learn('hello world', 'greetings') + + const revived = bayes.fromJson(classifier.toJson()) + expect(revived.alpha).toBe(2) + expect(revived.fitPrior).toBe(false) + }) +}) + +describe('bayes .learn() correctness', () => { + it('categorizes correctly for `positive` and `negative` categories', () => { + let classifier = bayes(); + + classifier.learn('amazing, awesome movie!! Yeah!!', 'positive') + classifier.learn('Sweet, this is incredibly, amazing, perfect, great!!', 'positive') + classifier.learn('terrible, shitty thing. Damn. Sucks!!', 'negative') + classifier.learn('I dont really know what to make of this.', 'neutral') + + expect(classifier.categorize('awesome, cool, amazing!! Yay.').predictedCategory).toEqual('positive') + }) + + it('categorizes correctly for `chinese` and `japanese` categories', () => { + const classifier = bayes() + + classifier.learn('Chinese Beijing Chinese', 'chinese') + classifier.learn('Chinese Chinese Shanghai', 'chinese') + classifier.learn('Chinese Macao', 'chinese') + classifier.learn('Tokyo Japan Chinese', 'japanese') + + const chineseFrequencyCount = classifier.wordFrequencyCount.chinese + + expect(chineseFrequencyCount['Chinese']).toBe(5) + expect(chineseFrequencyCount['Beijing']).toBe(1) + expect(chineseFrequencyCount['Shanghai']).toBe(1) + expect(chineseFrequencyCount['Macao']).toBe(1) + + const japaneseFrequencyCount = classifier.wordFrequencyCount.japanese + + expect(japaneseFrequencyCount['Tokyo']).toBe(1) + expect(japaneseFrequencyCount['Japan']).toBe(1) + expect(japaneseFrequencyCount['Chinese']).toBe(1) + + expect(classifier.categorize('Chinese Chinese Chinese Tokyo Japan').predictedCategory).toEqual('chinese') + }) + + it('correctly tokenizes cyrlic characters', () => { + const classifier = bayes() + + classifier.learn('Надежда за', 'a') + classifier.learn('Надежда за обич еп.36 Тест', 'b') + classifier.learn('Надежда за обич еп.36 Тест', 'b') + + const aFreqCount = classifier.wordFrequencyCount.a + expect(aFreqCount['Надежда']).toBe(1) + expect(aFreqCount['за']).toBe(1) + + const bFreqCount = classifier.wordFrequencyCount.b + expect(bFreqCount['Надежда']).toBe(2) + expect(bFreqCount['за']).toBe(2) + expect(bFreqCount['обич']).toBe(2) + expect(bFreqCount['еп']).toBe(2) + expect(bFreqCount['36']).toBe(2) + expect(bFreqCount['Тест']).toBe(2) + }) + + it('correctly computes probabilities without prior', () => { + const classifier = bayes({ fitPrior: false}) + + classifier.learn('aa', '1') + classifier.learn('aa', '1') + classifier.learn('aa', '1') + classifier.learn('bb', '2') + + expect(classifier.categorize('cc').likelihoods[0].proba).toBe(0.5) + expect(Number(classifier.categorize('bb').likelihoods[0].proba).toFixed(6)).toBe(Number(0.76923077).toFixed(6)) + expect(Number(classifier.categorize('aa').likelihoods[0].proba).toFixed(6)).toBe(Number(0.70588235).toFixed(6)) + }) + + it('correctly computes probabilities with prior', () => { + const classifier = bayes() + + classifier.learn('aa', '1') + classifier.learn('aa', '1') + classifier.learn('aa', '1') + classifier.learn('bb', '2') + + expect(classifier.categorize('cc').likelihoods[0].proba).toBe(0.75) + expect(Number(classifier.categorize('bb').likelihoods[0].proba).toFixed(6)).toBe(Number(0.52631579).toFixed(6)) + expect(Number(classifier.categorize('aa').likelihoods[0].proba).toFixed(6)).toBe(Number(0.87804878).toFixed(6)) + }) + + it('throws TypeError when text is not a string', () => { + const classifier = bayes() + expect(() => { classifier.learn(123, 'cat') }).toThrow(TypeError) + expect(() => { classifier.learn(null, 'cat') }).toThrow(TypeError) + }) + + it('throws TypeError when category is not a string', () => { + const classifier = bayes() + expect(() => { classifier.learn('hello', 123) }).toThrow(TypeError) + expect(() => { classifier.learn('hello', null) }).toThrow(TypeError) + }) +}) + +describe('bayes .unlearn() correctness', () => { + it('reverses the effect of a single learn call', () => { + const classifier = bayes() + + classifier.learn('fun times', 'positive') + classifier.learn('bad times', 'negative') + classifier.learn('great day', 'positive') + + const docsBefore = classifier.totalDocuments + classifier.unlearn('fun times', 'positive') + + expect(classifier.totalDocuments).toBe(docsBefore - 1) + }) + + it('throws when unlearning from a non-existent category', () => { + const classifier = bayes() + classifier.learn('hello', 'greetings') + + expect(() => { + classifier.unlearn('hello', 'nonexistent') + }).toThrow(Error) + }) + + it('removes category from categories when last doc is unlearned', () => { + const classifier = bayes() + + classifier.learn('hello', 'greetings') + expect(classifier.categories['greetings']).toBeTruthy() + + classifier.unlearn('hello', 'greetings') + expect(classifier.categories['greetings']).toBe(undefined) + }) + + it('classifier still works correctly after unlearn', () => { + const classifier = bayes() + + classifier.learn('amazing great', 'positive') + classifier.learn('terrible awful', 'negative') + classifier.learn('bad horrible', 'negative') + + classifier.unlearn('bad horrible', 'negative') + + const result = classifier.categorize('terrible') + expect(result.predictedCategory).toBe('negative') + }) + + it('returns this for method chaining', () => { + const classifier = bayes() + classifier.learn('hello', 'greetings') + const result = classifier.unlearn('hello', 'greetings') + expect(result).toBe(classifier) + }) + + it('throws TypeError when text is not a string', () => { + const classifier = bayes() + classifier.learn('hello', 'greetings') + expect(() => { classifier.unlearn(123, 'greetings') }).toThrow(TypeError) + }) + + it('does not leave negative wordCount', () => { + const classifier = bayes() + classifier.learn('hello', 'greetings') + classifier.unlearn('hello', 'greetings') + + expect(classifier.wordCount['greetings']).toBe(undefined) + }) +}) + +describe('bayes .removeCategory()', () => { + it('removes a category and its associated data', () => { + const classifier = bayes() + + classifier.learn('hello world', 'greetings') + classifier.learn('bad stuff', 'negative') + + classifier.removeCategory('greetings') + + expect(classifier.categories['greetings']).toBe(undefined) + expect(classifier.docCount['greetings']).toBe(undefined) + expect(classifier.wordCount['greetings']).toBe(undefined) + expect(classifier.wordFrequencyCount['greetings']).toBe(undefined) + }) + + it('returns this for chaining', () => { + const classifier = bayes() + classifier.learn('hello', 'greetings') + const result = classifier.removeCategory('greetings') + expect(result).toBe(classifier) + }) + + it('returns this when removing non-existent category (no-op)', () => { + const classifier = bayes() + const result = classifier.removeCategory('nonexistent') + expect(result).toBe(classifier) + }) + + it('classifier still categorizes correctly after removing a category', () => { + const classifier = bayes() + + classifier.learn('amazing great', 'positive') + classifier.learn('terrible bad', 'negative') + classifier.learn('meh ok', 'neutral') + + classifier.removeCategory('neutral') + + const result = classifier.categorize('amazing') + expect(result.predictedCategory).toBe('positive') + expect(result.likelihoods.length).toBe(2) + }) + + it('updates vocabulary and vocabularySize correctly', () => { + const classifier = bayes() + + classifier.learn('unique', 'only') + const sizeBefore = classifier.vocabularySize + + classifier.removeCategory('only') + expect(classifier.vocabularySize < sizeBefore).toBeTruthy() + }) + + it('does not produce negative vocabulary counts', () => { + const classifier = bayes() + + classifier.learn('shared word', 'a') + classifier.learn('shared word', 'b') + + classifier.removeCategory('a') + + // 'shared' and 'word' should still have count >= 0 + Object.keys(classifier.vocabulary).forEach((token) => { + expect(classifier.vocabulary[token] >= 0).toBeTruthy() + }) + }) +}) + +describe('bayes .categorize() return structure', () => { + it('returns an object with predictedCategory and likelihoods', () => { + const classifier = bayes() + classifier.learn('hello', 'greetings') + + const result = classifier.categorize('hello') + expect(result.hasOwnProperty('predictedCategory')).toBeTruthy() + expect(result.hasOwnProperty('likelihoods')).toBeTruthy() + expect(Array.isArray(result.likelihoods)).toBeTruthy() + }) + + it('likelihoods contain category, logLikelihood, logProba, proba', () => { + const classifier = bayes() + classifier.learn('hello', 'greetings') + + const result = classifier.categorize('hello') + const likelihood = result.likelihoods[0] + + expect(likelihood.hasOwnProperty('category')).toBeTruthy() + expect(likelihood.hasOwnProperty('logLikelihood')).toBeTruthy() + expect(likelihood.hasOwnProperty('logProba')).toBeTruthy() + expect(likelihood.hasOwnProperty('proba')).toBeTruthy() + }) + + it('likelihoods are sorted by proba descending', () => { + const classifier = bayes() + classifier.learn('aa', 'a') + classifier.learn('bb', 'b') + classifier.learn('cc', 'c') + + const result = classifier.categorize('aa') + for (let i = 1; i < result.likelihoods.length; i++) { + expect(result.likelihoods[i - 1].proba >= result.likelihoods[i].proba).toBeTruthy() + } + }) + + it('probabilities sum to approximately 1.0', () => { + const classifier = bayes() + classifier.learn('happy fun', 'positive') + classifier.learn('sad bad', 'negative') + + const result = classifier.categorize('happy') + const sum = result.likelihoods.reduce((acc, l) => { return acc + l.proba }, 0) + expect(Math.abs(sum - 1.0) < 0.001).toBeTruthy() + }) + + it('returns predictedCategory null for empty classifier', () => { + const classifier = bayes() + const result = classifier.categorize('hello') + + expect(result.predictedCategory).toBe(null) + expect(result.likelihoods).toEqual([]) + }) + + it('throws TypeError when text is not a string', () => { + const classifier = bayes() + expect(() => { classifier.categorize(123) }).toThrow(TypeError) + expect(() => { classifier.categorize(null) }).toThrow(TypeError) + }) +}) + +describe('bayes edge cases', () => { + it('handles empty string input to categorize()', () => { + const classifier = bayes() + classifier.learn('hello world', 'greetings') + + const result = classifier.categorize('') + expect(result.hasOwnProperty('predictedCategory')).toBeTruthy() + }) + + it('handles text with only punctuation', () => { + const classifier = bayes() + classifier.learn('hello', 'greetings') + + const result = classifier.categorize('!@#$%') + expect(result.hasOwnProperty('predictedCategory')).toBeTruthy() + }) + + it('handles unknown tokens gracefully', () => { + const classifier = bayes() + classifier.learn('hello world', 'greetings') + + const result = classifier.categorize('xyzzy foobar') + expect(result.hasOwnProperty('predictedCategory')).toBeTruthy() + }) +}) + +describe('bayes alpha parameter', () => { + it('uses default alpha of 1 when not specified', () => { + const classifier = bayes() + expect(classifier.alpha).toBe(1) + }) + + it('accepts alpha: 0 without overriding to default', () => { + const classifier = bayes({ alpha: 0 }) + expect(classifier.alpha).toBe(0) + }) + + it('custom alpha affects token probability calculation', () => { + const classifier1 = bayes({ alpha: 1 }) + const classifier2 = bayes({ alpha: 10 }) + + classifier1.learn('hello world', 'greetings') + classifier1.learn('goodbye world', 'farewells') + classifier2.learn('hello world', 'greetings') + classifier2.learn('goodbye world', 'farewells') + + const prob1 = classifier1.tokenProbability('hello', 'greetings') + const prob2 = classifier2.tokenProbability('hello', 'greetings') + + expect(prob1).not.toBe(prob2) + }) + + it('alpha: 0 categorization works but unseen tokens zero-out a category', () => { + const classifier = bayes({ alpha: 0 }) + + classifier.learn('happy fun', 'positive') + classifier.learn('sad bad', 'negative') + + // 'happy' was only seen in positive, so negative gets zero probability for it + const result = classifier.categorize('happy') + expect(result.predictedCategory).toBe('positive') + + // result should not contain NaN + result.likelihoods.forEach((l) => { + expect(!isNaN(l.proba)).toBeTruthy() + }) + }) +}) + +describe('bayes method chaining', () => { + it('learn() returns this', () => { + const classifier = bayes() + expect(classifier.learn('hello', 'greetings')).toBe(classifier) + }) + + it('initializeCategory() returns this', () => { + const classifier = bayes() + expect(classifier.initializeCategory('test')).toBe(classifier) + }) + + it('removeCategory() returns this', () => { + const classifier = bayes() + expect(classifier.removeCategory('test')).toBe(classifier) + }) + + it('supports fluent chain: learn().learn().categorize()', () => { + const result = bayes() + .learn('happy fun', 'positive') + .learn('sad bad', 'negative') + .categorize('happy') + + expect(result.predictedCategory).toBe('positive') + }) +}) + +describe('bayes .getCategories()', () => { + it('returns empty array for new classifier', () => { + const classifier = bayes() + expect(classifier.getCategories()).toEqual([]) + }) + + it('returns array of learned category names', () => { + const classifier = bayes() + classifier.learn('hello', 'greetings') + classifier.learn('bye', 'farewells') + + const categories = classifier.getCategories() + expect(categories.indexOf('greetings') !== -1).toBeTruthy() + expect(categories.indexOf('farewells') !== -1).toBeTruthy() + expect(categories.length).toBe(2) + }) + + it('reflects category removal', () => { + const classifier = bayes() + classifier.learn('hello', 'greetings') + classifier.learn('bye', 'farewells') + classifier.removeCategory('greetings') + + const categories = classifier.getCategories() + expect(categories.indexOf('greetings') === -1).toBeTruthy() + expect(categories.length).toBe(1) + }) +}) + +describe('bayes .categorizeTopN()', () => { + it('returns only top N categories', () => { + const classifier = bayes() + classifier.learn('aa', 'a') + classifier.learn('bb', 'b') + classifier.learn('cc', 'c') + + const result = classifier.categorizeTopN('aa', 2) + expect(result.likelihoods.length).toBe(2) + }) + + it('returns all categories if N >= total categories', () => { + const classifier = bayes() + classifier.learn('aa', 'a') + classifier.learn('bb', 'b') + + const result = classifier.categorizeTopN('aa', 10) + expect(result.likelihoods.length).toBe(2) + }) + + it('predictedCategory is the most likely', () => { + const classifier = bayes() + classifier.learn('happy fun great', 'positive') + classifier.learn('sad bad terrible', 'negative') + classifier.learn('ok meh whatever', 'neutral') + + const result = classifier.categorizeTopN('happy fun', 1) + expect(result.predictedCategory).toBe('positive') + expect(result.likelihoods.length).toBe(1) + }) +}) + +describe('bayes .categorizeWithConfidence()', () => { + it('returns predictedCategory when above threshold', () => { + const classifier = bayes() + classifier.learn('happy fun great amazing', 'positive') + classifier.learn('sad bad terrible awful', 'negative') + + const result = classifier.categorizeWithConfidence('happy fun great', 0.5) + expect(result.predictedCategory).toBe('positive') + }) + + it('returns null predictedCategory when below threshold', () => { + const classifier = bayes() + classifier.learn('aa', 'a') + classifier.learn('bb', 'b') + + // with a very high threshold, prediction should be null + const result = classifier.categorizeWithConfidence('cc', 0.99) + expect(result.predictedCategory).toBe(null) + }) + + it('returns null for empty classifier', () => { + const classifier = bayes() + const result = classifier.categorizeWithConfidence('hello', 0.5) + expect(result.predictedCategory).toBe(null) + }) + + it('still returns full likelihoods array', () => { + const classifier = bayes() + classifier.learn('aa', 'a') + classifier.learn('bb', 'b') + + const result = classifier.categorizeWithConfidence('cc', 0.99) + expect(Array.isArray(result.likelihoods)).toBeTruthy() + expect(result.likelihoods.length > 0).toBeTruthy() + }) + + it('throws TypeError for invalid threshold', () => { + const classifier = bayes() + classifier.learn('hello', 'greetings') + + expect(() => { classifier.categorizeWithConfidence('hello', -1) }).toThrow(TypeError) + expect(() => { classifier.categorizeWithConfidence('hello', 2) }).toThrow(TypeError) + expect(() => { classifier.categorizeWithConfidence('hello', 'bad') }).toThrow(TypeError) + }) +}) + +describe('bayes .topInfluentialTokens()', () => { + it('returns top tokens for a classification', () => { + const classifier = bayes() + classifier.learn('happy fun great joy', 'positive') + classifier.learn('sad bad terrible gloom', 'negative') + + const tokens = classifier.topInfluentialTokens('happy fun great', 3) + expect(Array.isArray(tokens)).toBeTruthy() + expect(tokens.length <= 3).toBeTruthy() + expect(tokens.length > 0).toBeTruthy() + + // each token should have the right shape + tokens.forEach((t) => { + expect(t.hasOwnProperty('token')).toBeTruthy() + expect(t.hasOwnProperty('probability')).toBeTruthy() + expect(t.hasOwnProperty('frequency')).toBeTruthy() + }) + }) + + it('returns empty array for empty classifier', () => { + const classifier = bayes() + const tokens = classifier.topInfluentialTokens('hello') + expect(tokens).toEqual([]) + }) + + it('tokens are sorted by probability descending', () => { + const classifier = bayes() + classifier.learn('apple banana cherry', 'fruit') + classifier.learn('dog cat bird', 'animal') + + const tokens = classifier.topInfluentialTokens('apple banana cherry', 5) + for (let i = 1; i < tokens.length; i++) { + expect(tokens[i - 1].probability >= tokens[i].probability).toBeTruthy() + } + }) + + it('defaults to 5 tokens', () => { + const classifier = bayes() + classifier.learn('a b c d e f g h', 'letters') + classifier.learn('1 2 3', 'numbers') + + const tokens = classifier.topInfluentialTokens('a b c d e f g h') + expect(tokens.length <= 5).toBeTruthy() + }) + + it('returns empty array when n is 0', () => { + const classifier = bayes() + classifier.learn('hello world', 'greetings') + + const tokens = classifier.topInfluentialTokens('hello world', 0) + expect(tokens).toEqual([]) + }) + + it('handles negative n by returning empty array', () => { + const classifier = bayes() + classifier.learn('hello world', 'greetings') + + const tokens = classifier.topInfluentialTokens('hello world', -3) + expect(tokens).toEqual([]) + }) +}) + +describe('bayes .learnBatch()', () => { + it('learns multiple items at once', () => { + const classifier = bayes() + classifier.learnBatch([ + { text: 'happy fun', category: 'positive' }, + { text: 'sad bad', category: 'negative' } + ]) + + expect(classifier.totalDocuments).toBe(2) + expect(classifier.categories['positive']).toBeTruthy() + expect(classifier.categories['negative']).toBeTruthy() + }) + + it('produces same result as individual learn calls', () => { + const classifier1 = bayes() + classifier1.learn('happy fun', 'positive') + classifier1.learn('sad bad', 'negative') + + const classifier2 = bayes() + classifier2.learnBatch([ + { text: 'happy fun', category: 'positive' }, + { text: 'sad bad', category: 'negative' } + ]) + + expect(classifier1.vocabulary).toEqual(classifier2.vocabulary) + expect(classifier1.totalDocuments).toBe(classifier2.totalDocuments) + expect(classifier1.docCount).toEqual(classifier2.docCount) + }) + + it('throws TypeError on non-array input', () => { + const classifier = bayes() + expect(() => { classifier.learnBatch('not an array') }).toThrow(TypeError) + expect(() => { classifier.learnBatch(42) }).toThrow(TypeError) + }) + + it('returns this for chaining', () => { + const classifier = bayes() + const result = classifier.learnBatch([{ text: 'hello', category: 'greetings' }]) + expect(result).toBe(classifier) + }) +}) + +describe('bayes .reset()', () => { + it('clears all learned data', () => { + const classifier = bayes() + classifier.learn('hello world', 'greetings') + classifier.learn('goodbye world', 'farewells') + + classifier.reset() + + expect(classifier.totalDocuments).toBe(0) + expect(classifier.vocabularySize).toBe(0) + expect(classifier.categories).toEqual({}) + expect(classifier.vocabulary).toEqual({}) + expect(classifier.docCount).toEqual({}) + expect(classifier.wordCount).toEqual({}) + expect(classifier.wordFrequencyCount).toEqual({}) + }) + + it('preserves options (tokenizer, alpha, fitPrior)', () => { + const customTokenizer = (text) => { return text.split('') } + const classifier = bayes({ tokenizer: customTokenizer, alpha: 2, fitPrior: false }) + classifier.learn('abc', 'letters') + + classifier.reset() + + expect(classifier.tokenizer).toBe(customTokenizer) + expect(classifier.alpha).toBe(2) + expect(classifier.fitPrior).toBe(false) + }) + + it('classifier can be retrained after reset', () => { + const classifier = bayes() + classifier.learn('hello', 'greetings') + classifier.reset() + classifier.learn('goodbye', 'farewells') + + expect(classifier.totalDocuments).toBe(1) + expect(classifier.getCategories()).toEqual(['farewells']) + }) + + it('returns this for chaining', () => { + const classifier = bayes() + expect(classifier.reset()).toBe(classifier) + }) +}) + +describe('bayes .getCategoryStats()', () => { + it('returns correct doc and word counts per category', () => { + const classifier = bayes() + classifier.learn('hello world', 'greetings') + classifier.learn('goodbye world', 'farewells') + classifier.learn('hi there', 'greetings') + + const stats = classifier.getCategoryStats() + + expect(stats.greetings.docCount).toBe(2) + expect(stats.farewells.docCount).toBe(1) + expect(stats.greetings.wordCount > 0).toBeTruthy() + expect(stats.greetings.vocabularySize > 0).toBeTruthy() + }) + + it('includes _total aggregate stats with wordCount', () => { + const classifier = bayes() + classifier.learn('hello world', 'greetings') + classifier.learn('bye now', 'farewells') + + const stats = classifier.getCategoryStats() + + expect(stats._total.docCount).toBe(2) + expect(stats._total.vocabularySize > 0).toBeTruthy() + expect(stats._total.wordCount).toBe(stats.greetings.wordCount + stats.farewells.wordCount) + }) +}) + +describe('bayes numerical stability (logsumexp)', () => { + it('probabilities still sum to ~1.0 with many categories', () => { + const classifier = bayes() + for (let i = 0; i < 20; i++) { + classifier.learn('word' + i + ' common shared text', 'cat' + i) + } + + const result = classifier.categorize('word0 common shared') + const sum = result.likelihoods.reduce((acc, l) => { return acc + l.proba }, 0) + expect(Math.abs(sum - 1.0) < 0.01).toBeTruthy() + }) + + it('handles long documents without NaN', () => { + const classifier = bayes() + classifier.learn('good great amazing wonderful fantastic', 'positive') + classifier.learn('bad terrible awful horrible dreadful', 'negative') + + // create a long document + let longText = '' + for (let i = 0; i < 100; i++) { + longText += 'good great amazing ' + } + + const result = classifier.categorize(longText) + expect(!isNaN(result.likelihoods[0].proba)).toBeTruthy() + expect(result.likelihoods[0].proba > 0).toBeTruthy() + expect(result.predictedCategory).toBe('positive') + }) +}) diff --git a/test/dist.test.ts b/test/dist.test.ts new file mode 100644 index 0000000..34f2abc --- /dev/null +++ b/test/dist.test.ts @@ -0,0 +1,107 @@ +import { describe, it, expect, beforeAll } from 'vitest' +import { execSync } from 'child_process' + +// These tests verify the actual compiled dist/ output works correctly. +// They run after build to catch packaging issues that source-level tests miss. + +beforeAll(() => { + execSync('npm run build', { stdio: 'ignore' }) +}) + +describe('[Dist] CJS require compatibility', () => { + it('require() returns a callable factory function', () => { + // eslint-disable-next-line @typescript-eslint/no-require-imports + const bayes = require('../dist/index.cjs') + expect(typeof bayes).toBe('function') + }) + + it('factory creates a working classifier', () => { + const bayes = require('../dist/index.cjs') + const c = bayes() + expect(typeof c.learn).toBe('function') + expect(typeof c.categorize).toBe('function') + expect(typeof c.toJson).toBe('function') + }) + + it('fromJson is attached to factory', () => { + const bayes = require('../dist/index.cjs') + expect(typeof bayes.fromJson).toBe('function') + }) + + it('STATE_KEYS is attached to factory', () => { + const bayes = require('../dist/index.cjs') + expect(Array.isArray(bayes.STATE_KEYS)).toBe(true) + expect(bayes.STATE_KEYS.length).toBe(8) + }) + + it('full learn → categorize cycle works', () => { + const bayes = require('../dist/index.cjs') + const c = bayes() + c.learn('happy fun great', 'positive') + c.learn('sad bad terrible', 'negative') + const result = c.categorize('happy fun') + expect(result.predictedCategory).toBe('positive') + expect(result.likelihoods.length).toBe(2) + }) + + it('serialization round-trip works', () => { + const bayes = require('../dist/index.cjs') + const c = bayes() + c.learn('hello world', 'greetings') + const json = c.toJson() + const restored = bayes.fromJson(json) + expect(restored.categorize('hello').predictedCategory).toBe('greetings') + }) +}) + +describe('[Dist] ESM import compatibility', () => { + it('default import returns a callable factory', async () => { + const mod = await import('../dist/index.js') + expect(typeof mod.default).toBe('function') + }) + + it('named exports are available', async () => { + const mod = await import('../dist/index.js') + expect(typeof mod.Naivebayes).toBe('function') + expect(typeof mod.fromJson).toBe('function') + expect(Array.isArray(mod.STATE_KEYS)).toBe(true) + }) + + it('factory creates a working classifier via ESM', async () => { + const mod = await import('../dist/index.js') + const bayes = mod.default + const c = bayes() + c.learn('good great', 'positive') + c.learn('bad awful', 'negative') + expect(c.categorize('good').predictedCategory).toBe('positive') + }) + + it('Naivebayes class can be instantiated directly', async () => { + const { Naivebayes } = await import('../dist/index.js') + const c = new Naivebayes() + c.learn('hello', 'greetings') + expect(c.categorize('hello').predictedCategory).toBe('greetings') + }) +}) + +describe('[Dist] Type declarations exist', () => { + it('d.ts files are generated', () => { + const fs = require('fs') + expect(fs.existsSync('dist/index.d.ts')).toBe(true) + expect(fs.existsSync('dist/index.d.cts')).toBe(true) + }) + + it('d.ts exports expected types', () => { + const fs = require('fs') + const dts = fs.readFileSync('dist/index.d.ts', 'utf8') + expect(dts).toContain('NaivebayesOptions') + expect(dts).toContain('CategorizeResult') + expect(dts).toContain('Likelihood') + expect(dts).toContain('InfluentialToken') + expect(dts).toContain('CategoryStats') + expect(dts).toContain('BatchItem') + expect(dts).toContain('Naivebayes') + expect(dts).toContain('fromJson') + expect(dts).toContain('STATE_KEYS') + }) +}) diff --git a/test/integration.js b/test/integration.test.ts similarity index 50% rename from test/integration.js rename to test/integration.test.ts index 75a2a62..10c2e7e 100644 --- a/test/integration.js +++ b/test/integration.test.ts @@ -1,21 +1,22 @@ -var assert = require('assert') - , bayes = require('../lib/classificator') +import { describe, it, beforeEach, expect } from 'vitest' +import bayes from '../src/index' +import type { Naivebayes } from '../src/index' // ============================================================================= // Integration Tests // Test multiple features working together in realistic combinations // ============================================================================= -describe('[Integration] full train → serialize → restore → classify pipeline', function () { - it('classifier survives a full round-trip with custom options', function () { - var tokenizer = function (text) { return text.toLowerCase().split(/\s+/) } - var preprocessor = function (tokens) { - var stops = new Set(['the', 'a', 'is', 'it', 'and', 'of', 'to', 'in']) - return tokens.filter(function (t) { return !stops.has(t) }) +describe('[Integration] full train → serialize → restore → classify pipeline', () => { + it('classifier survives a full round-trip with custom options', () => { + const tokenizer = (text: string) => { return text.toLowerCase().split(/\s+/) } + const preprocessor = (tokens: string[]) => { + const stops = new Set(['the', 'a', 'is', 'it', 'and', 'of', 'to', 'in']) + return tokens.filter((t) => { return !stops.has(t) }) } // 1. Create with custom options - var classifier = bayes({ + const classifier = bayes({ tokenizer: tokenizer, tokenPreprocessor: preprocessor, alpha: 0.5, @@ -29,72 +30,73 @@ describe('[Integration] full train → serialize → restore → classify pipeli classifier.learn('It is a bad film, awful acting', 'negative') // 3. Verify pre-serialization - var before = classifier.categorize('amazing film') - assert.equal(before.predictedCategory, 'positive') + const before = classifier.categorize('amazing film') + expect(before.predictedCategory).toBe('positive') // 4. Serialize - var json = classifier.toJson() + const json = classifier.toJson() // 5. Restore with runtime options - var restored = bayes.fromJson(json, { + const restored = bayes.fromJson(json, { tokenizer: tokenizer, tokenPreprocessor: preprocessor }) // 6. Verify post-restoration - var after = restored.categorize('amazing film') - assert.equal(after.predictedCategory, 'positive') - assert.equal(after.likelihoods.length, before.likelihoods.length) + const after = restored.categorize('amazing film') + expect(after.predictedCategory).toBe('positive') + expect(after.likelihoods.length).toBe(before.likelihoods.length) // 7. Probabilities should match - assert.equal( - before.likelihoods[0].proba.toFixed(8), + expect( + before.likelihoods[0].proba.toFixed(8) + ).toBe( after.likelihoods[0].proba.toFixed(8) ) // 8. Options preserved - assert.equal(restored.alpha, 0.5) - assert.equal(restored.fitPrior, true) - assert.strictEqual(restored.tokenizer, tokenizer) - assert.strictEqual(restored.tokenPreprocessor, preprocessor) + expect(restored.alpha).toBe(0.5) + expect(restored.fitPrior).toBe(true) + expect(restored.tokenizer).toBe(tokenizer) + expect(restored.tokenPreprocessor).toBe(preprocessor) }) }) -describe('[Integration] learn → unlearn → relearn cycle', function () { - it('classifier state is consistent after learn/unlearn/relearn', function () { - var classifier = bayes() +describe('[Integration] learn → unlearn → relearn cycle', () => { + it('classifier state is consistent after learn/unlearn/relearn', () => { + const classifier = bayes() // learn initial data classifier.learn('good morning sunshine', 'positive') classifier.learn('terrible horrible day', 'negative') classifier.learn('wonderful great time', 'positive') - var stats1 = classifier.getCategoryStats() - assert.equal(stats1._total.docCount, 3) + const stats1 = classifier.getCategoryStats() + expect(stats1._total.docCount).toBe(3) // unlearn a mistake classifier.unlearn('wonderful great time', 'positive') - assert.equal(classifier.totalDocuments, 2) + expect(classifier.totalDocuments).toBe(2) // re-learn corrected data classifier.learn('wonderful great time', 'neutral') - assert.equal(classifier.totalDocuments, 3) + expect(classifier.totalDocuments).toBe(3) // classifier should now have 3 categories - var categories = classifier.getCategories() - assert.ok(categories.indexOf('positive') !== -1) - assert.ok(categories.indexOf('negative') !== -1) - assert.ok(categories.indexOf('neutral') !== -1) + const categories = classifier.getCategories() + expect(categories.indexOf('positive') !== -1).toBeTruthy() + expect(categories.indexOf('negative') !== -1).toBeTruthy() + expect(categories.indexOf('neutral') !== -1).toBeTruthy() // classification still works - var result = classifier.categorize('terrible') - assert.equal(result.predictedCategory, 'negative') + const result = classifier.categorize('terrible') + expect(result.predictedCategory).toBe('negative') }) }) -describe('[Integration] batch learning + stats + reset + retrain', function () { - it('full lifecycle: batch train, inspect, reset, retrain differently', function () { - var classifier = bayes() +describe('[Integration] batch learning + stats + reset + retrain', () => { + it('full lifecycle: batch train, inspect, reset, retrain differently', () => { + const classifier = bayes() // batch train classifier.learnBatch([ @@ -106,32 +108,32 @@ describe('[Integration] batch learning + stats + reset + retrain', function () { ]) // inspect - var stats = classifier.getCategoryStats() - assert.equal(stats.spam.docCount, 2) - assert.equal(stats.ham.docCount, 3) - assert.equal(stats._total.docCount, 5) + const stats = classifier.getCategoryStats() + expect(stats.spam.docCount).toBe(2) + expect(stats.ham.docCount).toBe(3) + expect(stats._total.docCount).toBe(5) // classify - assert.equal(classifier.categorize('free offer').predictedCategory, 'spam') - assert.equal(classifier.categorize('meeting tomorrow').predictedCategory, 'ham') + expect(classifier.categorize('free offer').predictedCategory).toBe('spam') + expect(classifier.categorize('meeting tomorrow').predictedCategory).toBe('ham') // reset classifier.reset() - assert.equal(classifier.totalDocuments, 0) - assert.deepEqual(classifier.getCategories(), []) + expect(classifier.totalDocuments).toBe(0) + expect(classifier.getCategories()).toEqual([]) // retrain with different categories classifier.learn('breaking news politics', 'news') classifier.learn('football scores today', 'sports') - assert.equal(classifier.categorize('political news').predictedCategory, 'news') - assert.equal(classifier.categorize('football game').predictedCategory, 'sports') + expect(classifier.categorize('political news').predictedCategory).toBe('news') + expect(classifier.categorize('football game').predictedCategory).toBe('sports') }) }) -describe('[Integration] removeCategory + reclassification', function () { - it('removing a dominant category shifts predictions correctly', function () { - var classifier = bayes() +describe('[Integration] removeCategory + reclassification', () => { + it('removing a dominant category shifts predictions correctly', () => { + const classifier = bayes() classifier.learn('python code function', 'programming') classifier.learn('java class object', 'programming') @@ -140,69 +142,69 @@ describe('[Integration] removeCategory + reclassification', function () { classifier.learn('stock market investment', 'finance') // programming dominates with fitPrior - var before = classifier.categorize('new class today') - assert.equal(before.predictedCategory, 'programming') - assert.equal(before.likelihoods.length, 3) + const before = classifier.categorize('new class today') + expect(before.predictedCategory).toBe('programming') + expect(before.likelihoods.length).toBe(3) // remove programming classifier.removeCategory('programming') // now should choose between cooking and finance - var after = classifier.categorize('new class today') - assert.equal(after.likelihoods.length, 2) - assert.ok(after.predictedCategory === 'cooking' || after.predictedCategory === 'finance') + const after = classifier.categorize('new class today') + expect(after.likelihoods.length).toBe(2) + expect(after.predictedCategory === 'cooking' || after.predictedCategory === 'finance').toBeTruthy() // probabilities still sum to ~1 - var sum = after.likelihoods.reduce(function (acc, l) { return acc + l.proba }, 0) - assert.ok(Math.abs(sum - 1.0) < 0.01) + const sum = after.likelihoods.reduce((acc, l) => { return acc + l.proba }, 0) + expect(Math.abs(sum - 1.0) < 0.01).toBeTruthy() }) }) -describe('[Integration] tokenPreprocessor affects all operations consistently', function () { - it('preprocessor is applied in learn, unlearn, categorize, and topInfluentialTokens', function () { - var lowered = [] - var preprocessor = function (tokens) { - var result = tokens.map(function (t) { return t.toLowerCase() }) +describe('[Integration] tokenPreprocessor affects all operations consistently', () => { + it('preprocessor is applied in learn, unlearn, categorize, and topInfluentialTokens', () => { + let lowered: string[][] = [] + const preprocessor = (tokens: string[]) => { + const result = tokens.map((t) => { return t.toLowerCase() }) lowered.push(result) return result } - var classifier = bayes({ tokenPreprocessor: preprocessor }) + const classifier = bayes({ tokenPreprocessor: preprocessor }) // learn — preprocessor called lowered = [] classifier.learn('HELLO WORLD', 'greetings') - assert.ok(lowered.length > 0) - assert.deepEqual(lowered[0], ['hello', 'world']) + expect(lowered.length > 0).toBeTruthy() + expect(lowered[0]).toEqual(['hello', 'world']) // should have lowercase tokens in vocabulary - assert.ok(classifier.vocabulary['hello']) - assert.equal(classifier.vocabulary['HELLO'], undefined) + expect(classifier.vocabulary['hello']).toBeTruthy() + expect(classifier.vocabulary['HELLO']).toBe(undefined) // categorize — preprocessor called lowered = [] - var result = classifier.categorize('HELLO') - assert.ok(lowered.length > 0) - assert.equal(result.predictedCategory, 'greetings') + const result = classifier.categorize('HELLO') + expect(lowered.length > 0).toBeTruthy() + expect(result.predictedCategory).toBe('greetings') // topInfluentialTokens — preprocessor called lowered = [] - var tokens = classifier.topInfluentialTokens('HELLO WORLD', 2) - assert.ok(lowered.length > 0) - assert.ok(tokens.length > 0) + const tokens = classifier.topInfluentialTokens('HELLO WORLD', 2) + expect(lowered.length > 0).toBeTruthy() + expect(tokens.length > 0).toBeTruthy() // unlearn — preprocessor called (lowercase matches original learn) lowered = [] classifier.learn('BYE NOW', 'farewells') classifier.unlearn('HELLO WORLD', 'greetings') - assert.ok(lowered.length > 0) - assert.equal(classifier.categories['greetings'], undefined) + expect(lowered.length > 0).toBeTruthy() + expect(classifier.categories['greetings']).toBe(undefined) }) }) -describe('[Integration] confidence threshold + topN combined workflow', function () { - it('uses confidence to filter then topN to limit results', function () { - var classifier = bayes() +describe('[Integration] confidence threshold + topN combined workflow', () => { + it('uses confidence to filter then topN to limit results', () => { + const classifier = bayes() classifier.learnBatch([ { text: 'cat dog hamster', category: 'pets' }, @@ -213,23 +215,23 @@ describe('[Integration] confidence threshold + topN combined workflow', function ]) // high confidence prediction - var confident = classifier.categorizeWithConfidence('cat dog', 0.3) - assert.equal(confident.predictedCategory, 'pets') + const confident = classifier.categorizeWithConfidence('cat dog', 0.3) + expect(confident.predictedCategory).toBe('pets') // low confidence for ambiguous text - var unsure = classifier.categorizeWithConfidence('xyz unknown', 0.5) - assert.equal(unsure.predictedCategory, null) + const unsure = classifier.categorizeWithConfidence('xyz unknown', 0.5) + expect(unsure.predictedCategory).toBe(null) // topN limits output - var top2 = classifier.categorizeTopN('cat dog', 2) - assert.equal(top2.likelihoods.length, 2) - assert.equal(top2.predictedCategory, 'pets') + const top2 = classifier.categorizeTopN('cat dog', 2) + expect(top2.likelihoods.length).toBe(2) + expect(top2.predictedCategory).toBe('pets') }) }) -describe('[Integration] method chaining complex workflow', function () { - it('chains learn, unlearn, removeCategory, and ends with categorize', function () { - var result = bayes() +describe('[Integration] method chaining complex workflow', () => { + it('chains learn, unlearn, removeCategory, and ends with categorize', () => { + const result = bayes() .learn('happy joy love', 'positive') .learn('sad hate anger', 'negative') .learn('meh whatever ok', 'neutral') @@ -239,8 +241,8 @@ describe('[Integration] method chaining complex workflow', function () { .learn('wonderful amazing', 'positive') .categorize('love and joy') - assert.equal(result.predictedCategory, 'positive') - assert.equal(result.likelihoods.length, 2) + expect(result.predictedCategory).toBe('positive') + expect(result.likelihoods.length).toBe(2) }) }) @@ -249,10 +251,10 @@ describe('[Integration] method chaining complex workflow', function () { // Simulate real-world classification scenarios from start to finish // ============================================================================= -describe('[E2E] email spam detection', function () { - var classifier +describe('[E2E] email spam detection', () => { + let classifier: Naivebayes - beforeEach(function () { + beforeEach(() => { classifier = bayes() // train spam @@ -274,73 +276,69 @@ describe('[E2E] email spam detection', function () { ]) }) - it('correctly classifies obvious spam', function () { - assert.equal( - classifier.categorize('Buy now free offer limited time').predictedCategory, - 'spam' - ) + it('correctly classifies obvious spam', () => { + expect( + classifier.categorize('Buy now free offer limited time').predictedCategory + ).toBe('spam') }) - it('correctly classifies legitimate email', function () { - assert.equal( - classifier.categorize('Can we discuss the project tomorrow').predictedCategory, - 'ham' - ) + it('correctly classifies legitimate email', () => { + expect( + classifier.categorize('Can we discuss the project tomorrow').predictedCategory + ).toBe('ham') }) - it('handles ambiguous text with confidence check', function () { - var result = classifier.categorizeWithConfidence('please review this', 0.9) + it('handles ambiguous text with confidence check', () => { + const result = classifier.categorizeWithConfidence('please review this', 0.9) // ambiguous text should either be confident ham or rejected - assert.ok( + expect( result.predictedCategory === 'ham' || result.predictedCategory === null - ) + ).toBeTruthy() }) - it('explains predictions with influential tokens', function () { - var tokens = classifier.topInfluentialTokens('Buy now free offer', 3) - assert.ok(tokens.length > 0) + it('explains predictions with influential tokens', () => { + const tokens = classifier.topInfluentialTokens('Buy now free offer', 3) + expect(tokens.length > 0).toBeTruthy() // all tokens should have valid probabilities - tokens.forEach(function (t) { - assert.ok(t.probability > 0) - assert.ok(t.probability <= 1) + tokens.forEach((t) => { + expect(t.probability > 0).toBeTruthy() + expect(t.probability <= 1).toBeTruthy() }) }) - it('survives serialization round-trip and still classifies', function () { - var json = classifier.toJson() - var restored = bayes.fromJson(json) + it('survives serialization round-trip and still classifies', () => { + const json = classifier.toJson() + const restored = bayes.fromJson(json) - assert.equal( - restored.categorize('Buy now free offer limited time').predictedCategory, - 'spam' - ) - assert.equal( - restored.categorize('Can we discuss the project tomorrow').predictedCategory, - 'ham' - ) + expect( + restored.categorize('Buy now free offer limited time').predictedCategory + ).toBe('spam') + expect( + restored.categorize('Can we discuss the project tomorrow').predictedCategory + ).toBe('ham') }) - it('stats reflect training data', function () { - var stats = classifier.getCategoryStats() - assert.equal(stats.spam.docCount, 5) - assert.equal(stats.ham.docCount, 5) - assert.equal(stats._total.docCount, 10) - assert.ok(stats._total.wordCount > 0) - assert.ok(stats._total.vocabularySize > 0) + it('stats reflect training data', () => { + const stats = classifier.getCategoryStats() + expect(stats.spam.docCount).toBe(5) + expect(stats.ham.docCount).toBe(5) + expect(stats._total.docCount).toBe(10) + expect(stats._total.wordCount > 0).toBeTruthy() + expect(stats._total.vocabularySize > 0).toBeTruthy() }) }) -describe('[E2E] sentiment analysis', function () { - var classifier +describe('[E2E] sentiment analysis', () => { + let classifier: Naivebayes - beforeEach(function () { + beforeEach(() => { classifier = bayes({ - tokenPreprocessor: function (tokens) { - return tokens.map(function (t) { return t.toLowerCase() }) + tokenPreprocessor: (tokens) => { + return tokens.map((t) => { return t.toLowerCase() }) } }) - var trainingData = [ + const trainingData = [ { text: 'I love this product amazing quality', category: 'positive' }, { text: 'Great experience wonderful service', category: 'positive' }, { text: 'Excellent work very satisfied happy', category: 'positive' }, @@ -354,50 +352,50 @@ describe('[E2E] sentiment analysis', function () { classifier.learnBatch(trainingData) }) - it('classifies positive review correctly', function () { - var result = classifier.categorize('Amazing product love the quality') - assert.equal(result.predictedCategory, 'positive') - assert.ok(result.likelihoods[0].proba > 0.5) + it('classifies positive review correctly', () => { + const result = classifier.categorize('Amazing product love the quality') + expect(result.predictedCategory).toBe('positive') + expect(result.likelihoods[0].proba > 0.5).toBeTruthy() }) - it('classifies negative review correctly', function () { - var result = classifier.categorize('Terrible waste would not buy again') - assert.equal(result.predictedCategory, 'negative') - assert.ok(result.likelihoods[0].proba > 0.5) + it('classifies negative review correctly', () => { + const result = classifier.categorize('Terrible waste would not buy again') + expect(result.predictedCategory).toBe('negative') + expect(result.likelihoods[0].proba > 0.5).toBeTruthy() }) - it('probabilities always sum to 1', function () { - var texts = [ + it('probabilities always sum to 1', () => { + const texts = [ 'Amazing product love it', 'Terrible broken waste', 'Some random unrelated words', '' ] - texts.forEach(function (text) { - var result = classifier.categorize(text) + texts.forEach((text) => { + const result = classifier.categorize(text) if (result.likelihoods.length > 0) { - var sum = result.likelihoods.reduce(function (a, l) { return a + l.proba }, 0) - assert.ok(Math.abs(sum - 1.0) < 0.01, 'proba sum should be ~1, got ' + sum) + const sum = result.likelihoods.reduce((a, l) => { return a + l.proba }, 0) + expect(Math.abs(sum - 1.0) < 0.01).toBeTruthy() } }) }) - it('top influential tokens make semantic sense', function () { - var tokens = classifier.topInfluentialTokens('Amazing product love the quality', 3) - var tokenNames = tokens.map(function (t) { return t.token }) + it('top influential tokens make semantic sense', () => { + const tokens = classifier.topInfluentialTokens('Amazing product love the quality', 3) + const tokenNames = tokens.map((t) => { return t.token }) // at least one positive word should be influential - var positiveWords = ['amazing', 'love', 'quality', 'product'] - var hasPositive = tokenNames.some(function (t) { return positiveWords.indexOf(t) !== -1 }) - assert.ok(hasPositive, 'should have at least one positive word in influential tokens') + const positiveWords = ['amazing', 'love', 'quality', 'product'] + const hasPositive = tokenNames.some((t) => { return positiveWords.indexOf(t) !== -1 }) + expect(hasPositive).toBeTruthy() }) }) -describe('[E2E] multi-category topic classification', function () { - var classifier +describe('[E2E] multi-category topic classification', () => { + let classifier: Naivebayes - beforeEach(function () { + beforeEach(() => { classifier = bayes() classifier.learnBatch([ @@ -419,73 +417,68 @@ describe('[E2E] multi-category topic classification', function () { ]) }) - it('correctly classifies finance text', function () { - assert.equal( - classifier.categorize('investors concerned about market volatility').predictedCategory, - 'finance' - ) + it('correctly classifies finance text', () => { + expect( + classifier.categorize('investors concerned about market volatility').predictedCategory + ).toBe('finance') }) - it('correctly classifies science text', function () { - assert.equal( - classifier.categorize('researchers study new galaxy formation').predictedCategory, - 'science' - ) + it('correctly classifies science text', () => { + expect( + classifier.categorize('researchers study new galaxy formation').predictedCategory + ).toBe('science') }) - it('correctly classifies sports text', function () { - assert.equal( - classifier.categorize('team wins game scoring record').predictedCategory, - 'sports' - ) + it('correctly classifies sports text', () => { + expect( + classifier.categorize('team wins game scoring record').predictedCategory + ).toBe('sports') }) - it('correctly classifies technology text', function () { - assert.equal( - classifier.categorize('new AI software update released').predictedCategory, - 'technology' - ) + it('correctly classifies technology text', () => { + expect( + classifier.categorize('new AI software update released').predictedCategory + ).toBe('technology') }) - it('topN returns correct number of categories', function () { - var result = classifier.categorizeTopN('new technology update', 2) - assert.equal(result.likelihoods.length, 2) + it('topN returns correct number of categories', () => { + const result = classifier.categorizeTopN('new technology update', 2) + expect(result.likelihoods.length).toBe(2) // top result should be technology - assert.equal(result.predictedCategory, 'technology') + expect(result.predictedCategory).toBe('technology') }) - it('all 4 categories exist', function () { - var cats = classifier.getCategories() - assert.equal(cats.length, 4) - assert.ok(cats.indexOf('finance') !== -1) - assert.ok(cats.indexOf('science') !== -1) - assert.ok(cats.indexOf('sports') !== -1) - assert.ok(cats.indexOf('technology') !== -1) + it('all 4 categories exist', () => { + const cats = classifier.getCategories() + expect(cats.length).toBe(4) + expect(cats.indexOf('finance') !== -1).toBeTruthy() + expect(cats.indexOf('science') !== -1).toBeTruthy() + expect(cats.indexOf('sports') !== -1).toBeTruthy() + expect(cats.indexOf('technology') !== -1).toBeTruthy() }) - it('survives full serialize → restore → classify cycle', function () { - var json = classifier.toJson() - var restored = bayes.fromJson(json) + it('survives full serialize → restore → classify cycle', () => { + const json = classifier.toJson() + const restored = bayes.fromJson(json) - assert.equal( - restored.categorize('stock market investors').predictedCategory, - 'finance' - ) - assert.equal(restored.getCategories().length, 4) - assert.equal(restored.getCategoryStats()._total.docCount, 12) + expect( + restored.categorize('stock market investors').predictedCategory + ).toBe('finance') + expect(restored.getCategories().length).toBe(4) + expect(restored.getCategoryStats()._total.docCount).toBe(12) }) }) -describe('[E2E] incremental learning over time', function () { - it('classifier improves as more data is added', function () { - var classifier = bayes() +describe('[E2E] incremental learning over time', () => { + it('classifier improves as more data is added', () => { + const classifier = bayes() // start with minimal data classifier.learn('good', 'positive') classifier.learn('bad', 'negative') // ambiguous initially - var initial = classifier.categorize('good bad ugly') + const initial = classifier.categorize('good bad ugly') // add more training data incrementally classifier.learnBatch([ @@ -496,18 +489,18 @@ describe('[E2E] incremental learning over time', function () { ]) // should now clearly classify 'good' as positive - var improved = classifier.categorize('good') - assert.equal(improved.predictedCategory, 'positive') - assert.ok(improved.likelihoods[0].proba > 0.5) + const improved = classifier.categorize('good') + expect(improved.predictedCategory).toBe('positive') + expect(improved.likelihoods[0].proba > 0.5).toBeTruthy() // stats reflect all training - assert.equal(classifier.getCategoryStats()._total.docCount, 6) + expect(classifier.getCategoryStats()._total.docCount).toBe(6) }) }) -describe('[E2E] correcting classification mistakes', function () { - it('unlearn wrong data, re-learn correct data, verify improvement', function () { - var classifier = bayes() +describe('[E2E] correcting classification mistakes', () => { + it('unlearn wrong data, re-learn correct data, verify improvement', () => { + const classifier = bayes() // initial correct training classifier.learn('happy joy smile', 'positive') @@ -517,25 +510,25 @@ describe('[E2E] correcting classification mistakes', function () { classifier.learn('happy celebration party', 'negative') // mistake! // verify the mistake hurts classification - var before = classifier.categorize('happy celebration') + const before = classifier.categorize('happy celebration') // fix the mistake classifier.unlearn('happy celebration party', 'negative') classifier.learn('happy celebration party', 'positive') // verify correction helped - var after = classifier.categorize('happy celebration') - assert.equal(after.predictedCategory, 'positive') + const after = classifier.categorize('happy celebration') + expect(after.predictedCategory).toBe('positive') }) }) -describe('[E2E] fitPrior impact on imbalanced datasets', function () { - it('fitPrior=true favors the majority class on ambiguous input', function () { - var withPrior = bayes({ fitPrior: true }) - var withoutPrior = bayes({ fitPrior: false }) +describe('[E2E] fitPrior impact on imbalanced datasets', () => { + it('fitPrior=true favors the majority class on ambiguous input', () => { + const withPrior = bayes({ fitPrior: true }) + const withoutPrior = bayes({ fitPrior: false }) // heavily imbalanced: 10 positive, 1 negative - for (var i = 0; i < 10; i++) { + for (let i = 0; i < 10; i++) { withPrior.learn('word' + i, 'majority') withoutPrior.learn('word' + i, 'majority') } @@ -543,14 +536,14 @@ describe('[E2E] fitPrior impact on imbalanced datasets', function () { withoutPrior.learn('rare', 'minority') // ambiguous text (unknown word) - var resultPrior = withPrior.categorize('unknown') - var resultNoPrior = withoutPrior.categorize('unknown') + const resultPrior = withPrior.categorize('unknown') + const resultNoPrior = withoutPrior.categorize('unknown') // with prior, majority class should be strongly favored - assert.equal(resultPrior.predictedCategory, 'majority') - assert.ok(resultPrior.likelihoods[0].proba > 0.8) + expect(resultPrior.predictedCategory).toBe('majority') + expect(resultPrior.likelihoods[0].proba > 0.8).toBeTruthy() // without prior, both classes should be closer to equal - assert.equal(resultNoPrior.likelihoods[0].proba, 0.5) + expect(resultNoPrior.likelihoods[0].proba).toBe(0.5) }) }) diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..ec9a08f --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "esModuleInterop": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "outDir": "./dist", + "rootDir": "./src", + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "isolatedModules": true + }, + "include": ["src"], + "exclude": ["node_modules", "dist", "test"] +} diff --git a/tsup.config.ts b/tsup.config.ts new file mode 100644 index 0000000..84c4462 --- /dev/null +++ b/tsup.config.ts @@ -0,0 +1,24 @@ +import { defineConfig } from 'tsup' + +export default defineConfig({ + entry: ['src/index.ts'], + format: ['cjs', 'esm'], + dts: true, + splitting: false, + sourcemap: true, + clean: true, + target: 'node18', + outDir: 'dist', + onSuccess: async () => { + // Patch CJS output so `require('classificator')` returns the factory function directly. + // Insert before the sourcemap comment so debuggers can still find source maps. + const fs = await import('fs') + const cjs = fs.readFileSync('dist/index.cjs', 'utf8') + const patch = '\nif (module.exports.default) { Object.assign(module.exports.default, module.exports); module.exports = module.exports.default; }\n' + const sourcemapMatch = cjs.match(/\n\/\/# sourceMappingURL=.*$/) + const patched = sourcemapMatch + ? cjs.replace(sourcemapMatch[0], patch + sourcemapMatch[0]) + : cjs + patch + fs.writeFileSync('dist/index.cjs', patched) + }, +}) diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..ec92b19 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + globals: true, + include: ['test/**/*.test.ts'], + }, +}) diff --git a/yarn.lock b/yarn.lock index ebaef2f..e436060 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,578 +2,641 @@ # yarn lockfile v1 -"@ungap/promise-all-settled@1.1.2": - version "1.1.2" - resolved "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz" - integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q== +"@esbuild/darwin-arm64@0.27.4": + version "0.27.4" + resolved "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz" + integrity sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ== -ansi-colors@4.1.1: - version "4.1.1" - resolved "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz" - integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== - -ansi-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz" - integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= - -ansi-regex@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz" - integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== - -ansi-styles@^4.0.0, ansi-styles@^4.1.0: - version "4.3.0" - resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== +"@jridgewell/gen-mapping@^0.3.2": + version "0.3.13" + resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz" + integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA== dependencies: - color-convert "^2.0.1" + "@jridgewell/sourcemap-codec" "^1.5.0" + "@jridgewell/trace-mapping" "^0.3.24" -anymatch@~3.1.2: +"@jridgewell/resolve-uri@^3.1.0": version "3.1.2" - resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz" - integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - -argparse@^2.0.1: + resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0", "@jridgewell/sourcemap-codec@^1.5.5": + version "1.5.5" + resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz" + integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== + +"@jridgewell/trace-mapping@^0.3.24": + version "0.3.31" + resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz" + integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@rollup/rollup-darwin-arm64@4.60.0": + version "4.60.0" + resolved "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.0.tgz" + integrity sha512-qEF7CsKKzSRc20Ciu2Zw1wRrBz4g56F7r/vRwY430UPp/nt1x21Q/fpJ9N5l47WWvJlkNCPJz3QRVw008fi7yA== + +"@types/chai@^5.2.2": + version "5.2.3" + resolved "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz" + integrity sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA== + dependencies: + "@types/deep-eql" "*" + assertion-error "^2.0.1" + +"@types/deep-eql@*": + version "4.0.2" + resolved "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz" + integrity sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw== + +"@types/estree@^1.0.0", "@types/estree@1.0.8": + version "1.0.8" + resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz" + integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== + +"@types/node@^18.0.0 || ^20.0.0 || >=22.0.0", "@types/node@^20.19.0 || >=22.12.0", "@types/node@^22.0.0": + version "22.19.15" + resolved "https://registry.npmjs.org/@types/node/-/node-22.19.15.tgz" + integrity sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg== + dependencies: + undici-types "~6.21.0" + +"@vitest/expect@3.2.4": + version "3.2.4" + resolved "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz" + integrity sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig== + dependencies: + "@types/chai" "^5.2.2" + "@vitest/spy" "3.2.4" + "@vitest/utils" "3.2.4" + chai "^5.2.0" + tinyrainbow "^2.0.0" + +"@vitest/mocker@3.2.4": + version "3.2.4" + resolved "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz" + integrity sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ== + dependencies: + "@vitest/spy" "3.2.4" + estree-walker "^3.0.3" + magic-string "^0.30.17" + +"@vitest/pretty-format@^3.2.4", "@vitest/pretty-format@3.2.4": + version "3.2.4" + resolved "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz" + integrity sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA== + dependencies: + tinyrainbow "^2.0.0" + +"@vitest/runner@3.2.4": + version "3.2.4" + resolved "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz" + integrity sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ== + dependencies: + "@vitest/utils" "3.2.4" + pathe "^2.0.3" + strip-literal "^3.0.0" + +"@vitest/snapshot@3.2.4": + version "3.2.4" + resolved "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz" + integrity sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ== + dependencies: + "@vitest/pretty-format" "3.2.4" + magic-string "^0.30.17" + pathe "^2.0.3" + +"@vitest/spy@3.2.4": + version "3.2.4" + resolved "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz" + integrity sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw== + dependencies: + tinyspy "^4.0.3" + +"@vitest/utils@3.2.4": + version "3.2.4" + resolved "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz" + integrity sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA== + dependencies: + "@vitest/pretty-format" "3.2.4" + loupe "^3.1.4" + tinyrainbow "^2.0.0" + +acorn@^8.16.0: + version "8.16.0" + resolved "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz" + integrity sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw== + +any-promise@^1.0.0: + version "1.3.0" + resolved "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz" + integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== + +assertion-error@^2.0.1: version "2.0.1" - resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz" - integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== - -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" - integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== - -binary-extensions@^2.0.0: - version "2.2.0" - resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz" - integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + resolved "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz" + integrity sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA== -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== +bundle-require@^5.1.0: + version "5.1.0" + resolved "https://registry.npmjs.org/bundle-require/-/bundle-require-5.1.0.tgz" + integrity sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA== dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" + load-tsconfig "^0.2.3" -braces@~3.0.2: - version "3.0.2" - resolved "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== - dependencies: - fill-range "^7.0.1" +cac@^6.7.14: + version "6.7.14" + resolved "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz" + integrity sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ== -browser-stdout@1.3.1: - version "1.3.1" - resolved "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz" - integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== - -camelcase@^6.0.0: - version "6.2.0" - resolved "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz" - integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== +chai@^5.2.0: + version "5.3.3" + resolved "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz" + integrity sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw== + dependencies: + assertion-error "^2.0.1" + check-error "^2.1.1" + deep-eql "^5.0.1" + loupe "^3.1.0" + pathval "^2.0.0" -chalk@^4.1.0: - version "4.1.1" - resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz" - integrity sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -chokidar@3.5.2: - version "3.5.2" - resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz" - integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ== - dependencies: - anymatch "~3.1.2" - braces "~3.0.2" - glob-parent "~5.1.2" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.6.0" - optionalDependencies: - fsevents "~2.3.2" +check-error@^2.1.1: + version "2.1.3" + resolved "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz" + integrity sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA== -cliui@^7.0.2: - version "7.0.4" - resolved "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz" - integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== +chokidar@^4.0.3: + version "4.0.3" + resolved "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz" + integrity sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA== dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.0" - wrap-ansi "^7.0.0" + readdirp "^4.0.1" -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" +commander@^4.0.0: + version "4.1.1" + resolved "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz" + integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== -color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +confbox@^0.1.8: + version "0.1.8" + resolved "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz" + integrity sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w== -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= +consola@^3.4.0: + version "3.4.2" + resolved "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz" + integrity sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA== -debug@4.3.1: - version "4.3.1" - resolved "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz" - integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== +debug@^4.4.0, debug@^4.4.1: + version "4.4.3" + resolved "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz" + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== dependencies: - ms "2.1.2" - -decamelize@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz" - integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== + ms "^2.1.3" decimal.js@^10.0.0: version "10.0.1" resolved "https://registry.npmjs.org/decimal.js/-/decimal.js-10.0.1.tgz" integrity sha512-vklWB5C4Cj423xnaOtsUmAv0/7GqlXIgDv2ZKDyR64OV3OSzGHNx2mk4p/1EKnB5s70k73cIOOEcG9YzF0q4Lw== -diff@5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz" - integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -escalade@^3.1.1: - version "3.1.1" - resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz" - integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== - -escape-string-regexp@4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz" - integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== - -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== - dependencies: - to-regex-range "^5.0.1" - -find-up@5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz" - integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== - dependencies: - locate-path "^6.0.0" - path-exists "^4.0.0" - -flat@^5.0.2: +deep-eql@^5.0.1: version "5.0.2" - resolved "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz" - integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" - integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= - -fsevents@~2.3.2: - version "2.3.2" - resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz" - integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== - -get-caller-file@^2.0.5: - version "2.0.5" - resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz" - integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== - -glob-parent@~5.1.2: - version "5.1.2" - resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - -glob@7.1.7: - version "7.1.7" - resolved "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz" - integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -growl@1.10.5: - version "1.10.5" - resolved "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz" - integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -he@1.2.0: - version "1.2.0" - resolved "https://registry.npmjs.org/he/-/he-1.2.0.tgz" - integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz" - integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2: - version "2.0.4" - resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -is-binary-path@~2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz" - integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== - dependencies: - binary-extensions "^2.0.0" - -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" - integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= - -is-fullwidth-code-point@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz" - integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - -is-glob@^4.0.1, is-glob@~4.0.1: - version "4.0.1" - resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz" - integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== + resolved "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz" + integrity sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q== + +es-module-lexer@^1.7.0: + version "1.7.0" + resolved "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz" + integrity sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA== + +esbuild@^0.27.0, esbuild@>=0.18: + version "0.27.4" + resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz" + integrity sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ== + optionalDependencies: + "@esbuild/aix-ppc64" "0.27.4" + "@esbuild/android-arm" "0.27.4" + "@esbuild/android-arm64" "0.27.4" + "@esbuild/android-x64" "0.27.4" + "@esbuild/darwin-arm64" "0.27.4" + "@esbuild/darwin-x64" "0.27.4" + "@esbuild/freebsd-arm64" "0.27.4" + "@esbuild/freebsd-x64" "0.27.4" + "@esbuild/linux-arm" "0.27.4" + "@esbuild/linux-arm64" "0.27.4" + "@esbuild/linux-ia32" "0.27.4" + "@esbuild/linux-loong64" "0.27.4" + "@esbuild/linux-mips64el" "0.27.4" + "@esbuild/linux-ppc64" "0.27.4" + "@esbuild/linux-riscv64" "0.27.4" + "@esbuild/linux-s390x" "0.27.4" + "@esbuild/linux-x64" "0.27.4" + "@esbuild/netbsd-arm64" "0.27.4" + "@esbuild/netbsd-x64" "0.27.4" + "@esbuild/openbsd-arm64" "0.27.4" + "@esbuild/openbsd-x64" "0.27.4" + "@esbuild/openharmony-arm64" "0.27.4" + "@esbuild/sunos-x64" "0.27.4" + "@esbuild/win32-arm64" "0.27.4" + "@esbuild/win32-ia32" "0.27.4" + "@esbuild/win32-x64" "0.27.4" + +estree-walker@^3.0.3: + version "3.0.3" + resolved "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz" + integrity sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g== + dependencies: + "@types/estree" "^1.0.0" + +expect-type@^1.2.1: + version "1.3.0" + resolved "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz" + integrity sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA== + +fdir@^6.5.0: + version "6.5.0" + resolved "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz" + integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== + +fix-dts-default-cjs-exports@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/fix-dts-default-cjs-exports/-/fix-dts-default-cjs-exports-1.0.1.tgz" + integrity sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg== dependencies: - is-extglob "^2.1.1" + magic-string "^0.30.17" + mlly "^1.7.4" + rollup "^4.34.8" -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== +fsevents@~2.3.2, fsevents@~2.3.3: + version "2.3.3" + resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== -is-plain-obj@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz" - integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== - -is-unicode-supported@^0.1.0: - version "0.1.0" - resolved "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz" - integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" - integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= - -js-yaml@4.1.0: - version "4.1.0" - resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz" - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== - dependencies: - argparse "^2.0.1" - -locate-path@^6.0.0: - version "6.0.0" - resolved "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz" - integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== - dependencies: - p-locate "^5.0.0" - -log-symbols@4.1.0: - version "4.1.0" - resolved "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz" - integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== - dependencies: - chalk "^4.1.0" - is-unicode-supported "^0.1.0" - -minimatch@^3.0.4, minimatch@3.0.4: - version "3.0.4" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz" - integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== - dependencies: - brace-expansion "^1.1.7" - -mocha@^9.0.2: - version "9.0.2" - resolved "https://registry.npmjs.org/mocha/-/mocha-9.0.2.tgz" - integrity sha512-FpspiWU+UT9Sixx/wKimvnpkeW0mh6ROAKkIaPokj3xZgxeRhcna/k5X57jJghEr8X+Cgu/Vegf8zCX5ugSuTA== - dependencies: - "@ungap/promise-all-settled" "1.1.2" - ansi-colors "4.1.1" - browser-stdout "1.3.1" - chokidar "3.5.2" - debug "4.3.1" - diff "5.0.0" - escape-string-regexp "4.0.0" - find-up "5.0.0" - glob "7.1.7" - growl "1.10.5" - he "1.2.0" - js-yaml "4.1.0" - log-symbols "4.1.0" - minimatch "3.0.4" - ms "2.1.3" - nanoid "3.1.23" - serialize-javascript "6.0.0" - strip-json-comments "3.1.1" - supports-color "8.1.1" - which "2.0.2" - wide-align "1.1.3" - workerpool "6.1.5" - yargs "16.2.0" - yargs-parser "20.2.4" - yargs-unparser "2.0.0" - -ms@2.1.2: - version "2.1.2" - resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - -ms@2.1.3: +joycon@^3.1.1: + version "3.1.1" + resolved "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz" + integrity sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw== + +js-tokens@^9.0.1: + version "9.0.1" + resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz" + integrity sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ== + +lilconfig@^3.1.1: + version "3.1.3" + resolved "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz" + integrity sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw== + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +load-tsconfig@^0.2.3: + version "0.2.5" + resolved "https://registry.npmjs.org/load-tsconfig/-/load-tsconfig-0.2.5.tgz" + integrity sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg== + +loupe@^3.1.0, loupe@^3.1.4: + version "3.2.1" + resolved "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz" + integrity sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ== + +magic-string@^0.30.17: + version "0.30.21" + resolved "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz" + integrity sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.5" + +mlly@^1.7.4: + version "1.8.2" + resolved "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz" + integrity sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA== + dependencies: + acorn "^8.16.0" + pathe "^2.0.3" + pkg-types "^1.3.1" + ufo "^1.6.3" + +ms@^2.1.3: version "2.1.3" resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -nanoid@3.1.23: - version "3.1.23" - resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz" - integrity sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw== - -normalize-path@^3.0.0, normalize-path@~3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - -once@^1.3.0: - version "1.4.0" - resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= - dependencies: - wrappy "1" - -p-limit@^3.0.2: - version "3.1.0" - resolved "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz" - integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== +mz@^2.7.0: + version "2.7.0" + resolved "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz" + integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== dependencies: - yocto-queue "^0.1.0" + any-promise "^1.0.0" + object-assign "^4.0.1" + thenify-all "^1.0.0" -p-locate@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz" - integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== - dependencies: - p-limit "^3.0.2" - -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" - integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= - -picomatch@^2.0.4, picomatch@^2.2.1: - version "2.3.0" - resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz" - integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== - -randombytes@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz" - integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== - dependencies: - safe-buffer "^5.1.0" - -readdirp@~3.6.0: - version "3.6.0" - resolved "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz" - integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== - dependencies: - picomatch "^2.2.1" - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz" - integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= - -safe-buffer@^5.1.0: - version "5.2.1" - resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -serialize-javascript@6.0.0: - version "6.0.0" - resolved "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz" - integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag== - dependencies: - randombytes "^2.1.0" - -"string-width@^1.0.2 || 2": - version "2.1.1" - resolved "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz" - integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== - dependencies: - is-fullwidth-code-point "^2.0.0" - strip-ansi "^4.0.0" +nanoid@^3.3.11: + version "3.3.11" + resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz" + integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== -string-width@^4.1.0: - version "4.2.2" - resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz" - integrity sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.0" +object-assign@^4.0.1: + version "4.1.1" + resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== -string-width@^4.2.0: - version "4.2.2" - resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz" - integrity sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.0" +pathe@^2.0.1, pathe@^2.0.3: + version "2.0.3" + resolved "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz" + integrity sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w== -strip-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz" - integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= - dependencies: - ansi-regex "^3.0.0" +pathval@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz" + integrity sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ== -strip-ansi@^6.0.0: - version "6.0.0" - resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz" - integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== - dependencies: - ansi-regex "^5.0.0" +picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== -strip-json-comments@3.1.1: - version "3.1.1" - resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz" - integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== +"picomatch@^3 || ^4", picomatch@^4.0.2, picomatch@^4.0.3: + version "4.0.4" + resolved "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz" + integrity sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A== -supports-color@^7.1.0: - version "7.2.0" - resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz" - integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== - dependencies: - has-flag "^4.0.0" +pirates@^4.0.1: + version "4.0.7" + resolved "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz" + integrity sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA== -supports-color@8.1.1: - version "8.1.1" - resolved "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== +pkg-types@^1.3.1: + version "1.3.1" + resolved "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz" + integrity sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ== dependencies: - has-flag "^4.0.0" + confbox "^0.1.8" + mlly "^1.7.4" + pathe "^2.0.1" -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== +postcss-load-config@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz" + integrity sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g== dependencies: - is-number "^7.0.0" + lilconfig "^3.1.1" -which@2.0.2: - version "2.0.2" - resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== +postcss@^8.4.12, postcss@^8.5.6, postcss@>=8.0.9: + version "8.5.8" + resolved "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz" + integrity sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg== dependencies: - isexe "^2.0.0" + nanoid "^3.3.11" + picocolors "^1.1.1" + source-map-js "^1.2.1" -wide-align@1.1.3: - version "1.1.3" - resolved "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz" - integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== - dependencies: - string-width "^1.0.2 || 2" +readdirp@^4.0.1: + version "4.1.2" + resolved "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz" + integrity sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg== -workerpool@6.1.5: - version "6.1.5" - resolved "https://registry.npmjs.org/workerpool/-/workerpool-6.1.5.tgz" - integrity sha512-XdKkCK0Zqc6w3iTxLckiuJ81tiD/o5rBE/m+nXpRCB+/Sq4DqkfXZ/x0jW02DG1tGsfUGXbTJyZDP+eu67haSw== +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== +rollup@^4.34.8, rollup@^4.43.0: + version "4.60.0" + resolved "https://registry.npmjs.org/rollup/-/rollup-4.60.0.tgz" + integrity sha512-yqjxruMGBQJ2gG4HtjZtAfXArHomazDHoFwFFmZZl0r7Pdo7qCIXKqKHZc8yeoMgzJJ+pO6pEEHa+V7uzWlrAQ== dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrappy@1: - version "1.0.2" - resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= - -y18n@^5.0.5: - version "5.0.8" - resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz" - integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== - -yargs-parser@^20.2.2, yargs-parser@20.2.4: - version "20.2.4" - resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz" - integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== + "@types/estree" "1.0.8" + optionalDependencies: + "@rollup/rollup-android-arm-eabi" "4.60.0" + "@rollup/rollup-android-arm64" "4.60.0" + "@rollup/rollup-darwin-arm64" "4.60.0" + "@rollup/rollup-darwin-x64" "4.60.0" + "@rollup/rollup-freebsd-arm64" "4.60.0" + "@rollup/rollup-freebsd-x64" "4.60.0" + "@rollup/rollup-linux-arm-gnueabihf" "4.60.0" + "@rollup/rollup-linux-arm-musleabihf" "4.60.0" + "@rollup/rollup-linux-arm64-gnu" "4.60.0" + "@rollup/rollup-linux-arm64-musl" "4.60.0" + "@rollup/rollup-linux-loong64-gnu" "4.60.0" + "@rollup/rollup-linux-loong64-musl" "4.60.0" + "@rollup/rollup-linux-ppc64-gnu" "4.60.0" + "@rollup/rollup-linux-ppc64-musl" "4.60.0" + "@rollup/rollup-linux-riscv64-gnu" "4.60.0" + "@rollup/rollup-linux-riscv64-musl" "4.60.0" + "@rollup/rollup-linux-s390x-gnu" "4.60.0" + "@rollup/rollup-linux-x64-gnu" "4.60.0" + "@rollup/rollup-linux-x64-musl" "4.60.0" + "@rollup/rollup-openbsd-x64" "4.60.0" + "@rollup/rollup-openharmony-arm64" "4.60.0" + "@rollup/rollup-win32-arm64-msvc" "4.60.0" + "@rollup/rollup-win32-ia32-msvc" "4.60.0" + "@rollup/rollup-win32-x64-gnu" "4.60.0" + "@rollup/rollup-win32-x64-msvc" "4.60.0" + fsevents "~2.3.2" -yargs-unparser@2.0.0: +siginfo@^2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz" - integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA== - dependencies: - camelcase "^6.0.0" - decamelize "^4.0.0" - flat "^5.0.2" - is-plain-obj "^2.1.0" - -yargs@16.2.0: - version "16.2.0" - resolved "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz" - integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== - dependencies: - cliui "^7.0.2" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.0" - y18n "^5.0.5" - yargs-parser "^20.2.2" - -yocto-queue@^0.1.0: - version "0.1.0" - resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + resolved "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz" + integrity sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g== + +source-map-js@^1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz" + integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== + +source-map@^0.7.6: + version "0.7.6" + resolved "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz" + integrity sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ== + +stackback@0.0.2: + version "0.0.2" + resolved "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz" + integrity sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw== + +std-env@^3.9.0: + version "3.10.0" + resolved "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz" + integrity sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg== + +strip-literal@^3.0.0: + version "3.1.0" + resolved "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz" + integrity sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg== + dependencies: + js-tokens "^9.0.1" + +sucrase@^3.35.0: + version "3.35.1" + resolved "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz" + integrity sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.2" + commander "^4.0.0" + lines-and-columns "^1.1.6" + mz "^2.7.0" + pirates "^4.0.1" + tinyglobby "^0.2.11" + ts-interface-checker "^0.1.9" + +thenify-all@^1.0.0: + version "1.6.0" + resolved "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz" + integrity sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA== + dependencies: + thenify ">= 3.1.0 < 4" + +"thenify@>= 3.1.0 < 4": + version "3.3.1" + resolved "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz" + integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== + dependencies: + any-promise "^1.0.0" + +tinybench@^2.9.0: + version "2.9.0" + resolved "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz" + integrity sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg== + +tinyexec@^0.3.2: + version "0.3.2" + resolved "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz" + integrity sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA== + +tinyglobby@^0.2.11, tinyglobby@^0.2.14, tinyglobby@^0.2.15: + version "0.2.15" + resolved "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz" + integrity sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ== + dependencies: + fdir "^6.5.0" + picomatch "^4.0.3" + +tinypool@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz" + integrity sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg== + +tinyrainbow@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz" + integrity sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw== + +tinyspy@^4.0.3: + version "4.0.4" + resolved "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz" + integrity sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q== + +tree-kill@^1.2.2: + version "1.2.2" + resolved "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz" + integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== + +ts-interface-checker@^0.1.9: + version "0.1.13" + resolved "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz" + integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA== + +tsup@^8.5.0: + version "8.5.1" + resolved "https://registry.npmjs.org/tsup/-/tsup-8.5.1.tgz" + integrity sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing== + dependencies: + bundle-require "^5.1.0" + cac "^6.7.14" + chokidar "^4.0.3" + consola "^3.4.0" + debug "^4.4.0" + esbuild "^0.27.0" + fix-dts-default-cjs-exports "^1.0.0" + joycon "^3.1.1" + picocolors "^1.1.1" + postcss-load-config "^6.0.1" + resolve-from "^5.0.0" + rollup "^4.34.8" + source-map "^0.7.6" + sucrase "^3.35.0" + tinyexec "^0.3.2" + tinyglobby "^0.2.11" + tree-kill "^1.2.2" + +typescript@^5.8.0, typescript@>=4.5.0: + version "5.9.3" + resolved "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz" + integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw== + +ufo@^1.6.3: + version "1.6.3" + resolved "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz" + integrity sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q== + +undici-types@~6.21.0: + version "6.21.0" + resolved "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz" + integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== + +vite-node@3.2.4: + version "3.2.4" + resolved "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz" + integrity sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg== + dependencies: + cac "^6.7.14" + debug "^4.4.1" + es-module-lexer "^1.7.0" + pathe "^2.0.3" + vite "^5.0.0 || ^6.0.0 || ^7.0.0-0" + +"vite@^5.0.0 || ^6.0.0 || ^7.0.0-0": + version "7.3.1" + resolved "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz" + integrity sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA== + dependencies: + esbuild "^0.27.0" + fdir "^6.5.0" + picomatch "^4.0.3" + postcss "^8.5.6" + rollup "^4.43.0" + tinyglobby "^0.2.15" + optionalDependencies: + fsevents "~2.3.3" + +vitest@^3.0.0: + version "3.2.4" + resolved "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz" + integrity sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A== + dependencies: + "@types/chai" "^5.2.2" + "@vitest/expect" "3.2.4" + "@vitest/mocker" "3.2.4" + "@vitest/pretty-format" "^3.2.4" + "@vitest/runner" "3.2.4" + "@vitest/snapshot" "3.2.4" + "@vitest/spy" "3.2.4" + "@vitest/utils" "3.2.4" + chai "^5.2.0" + debug "^4.4.1" + expect-type "^1.2.1" + magic-string "^0.30.17" + pathe "^2.0.3" + picomatch "^4.0.2" + std-env "^3.9.0" + tinybench "^2.9.0" + tinyexec "^0.3.2" + tinyglobby "^0.2.14" + tinypool "^1.1.1" + tinyrainbow "^2.0.0" + vite "^5.0.0 || ^6.0.0 || ^7.0.0-0" + vite-node "3.2.4" + why-is-node-running "^2.3.0" + +why-is-node-running@^2.3.0: + version "2.3.0" + resolved "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz" + integrity sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w== + dependencies: + siginfo "^2.0.0" + stackback "0.0.2"