From 63f51142a8acf81b95ec76737dc37aab4feb378b Mon Sep 17 00:00:00 2001 From: Jonas Alves Date: Sat, 28 Mar 2026 16:57:48 +0000 Subject: [PATCH 01/22] feat: scaffold TypeScript project with tsup, vitest, and modern config --- .browserslistrc | 1 - .editorconfig | 10 - .eslintignore | 2 - .eslintrc.js | 58 - .prettierignore | 5 - .prettierrc.json | 7 - babel.config.js | 100 - jest.config.js | 10 - package-lock.json | 19488 +++++----------------------- package.json | 154 +- scripts/generate-version.ts | 4 + src/__tests__/placeholder.test.ts | 5 + src/index.ts | 11 +- src/version.ts | 2 +- tsconfig.json | 53 +- tsup.config.ts | 16 + vitest.config.ts | 13 + webpack.config.js | 55 - 18 files changed, 3024 insertions(+), 16970 deletions(-) delete mode 100644 .browserslistrc delete mode 100644 .editorconfig delete mode 100644 .eslintignore delete mode 100644 .eslintrc.js delete mode 100644 .prettierignore delete mode 100644 .prettierrc.json delete mode 100644 babel.config.js delete mode 100644 jest.config.js create mode 100644 scripts/generate-version.ts create mode 100644 src/__tests__/placeholder.test.ts create mode 100644 tsup.config.ts create mode 100644 vitest.config.ts delete mode 100644 webpack.config.js diff --git a/.browserslistrc b/.browserslistrc deleted file mode 100644 index 755f8b8..0000000 --- a/.browserslistrc +++ /dev/null @@ -1 +0,0 @@ -IE 10 diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index ffd3563..0000000 --- a/.editorconfig +++ /dev/null @@ -1,10 +0,0 @@ -root = true - -[*.{json,js,sh}] -end_of_line = lf -insert_final_newline = true -indent_style = tab -indent_size = 4 -trim_trailing_whitespace = true -charset = utf-8 - diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 12289b4..0000000 --- a/.eslintignore +++ /dev/null @@ -1,2 +0,0 @@ -/dist/ -/src/js/ diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index f5c917c..0000000 --- a/.eslintrc.js +++ /dev/null @@ -1,58 +0,0 @@ -module.exports = { - root: true, - parser: "@typescript-eslint/parser", - plugins: ["@typescript-eslint"], - env: { - browser: true, - node: true, - es6: true, - jest: true, - }, - extends: [ - "eslint:recommended", - "prettier", - "plugin:@typescript-eslint/eslint-recommended", - "plugin:@typescript-eslint/recommended", - ], - rules: { - "@typescript-eslint/no-empty-function": "off", - "no-debugger": process.env.NODE_ENV === "production" ? "error" : "off", - "no-template-curly-in-string": "error", - "no-promise-executor-return": "error", - "no-useless-backreference": "error", - // "require-atomic-updates": "error", - "array-callback-return": "error", - "block-scoped-var": "error", - "class-methods-use-this": "off", - "consistent-return": "error", - "default-case": ["error", { commentPattern: "^skip|no\\s+default" }], - "default-param-last": "error", - "dot-location": ["error", "property"], - eqeqeq: ["error", "smart"], - "no-alert": "error", - "no-constructor-return": "error", - "no-else-return": "error", - "no-extend-native": "error", - "no-extra-label": "error", - "no-invalid-this": "error", - "no-loop-func": "error", - "no-return-assign": "error", - "no-return-await": "error", - "no-self-compare": "error", - "no-sequences": "error", - "no-throw-literal": "error", - "no-useless-concat": "error", - "no-useless-return": "error", - "no-void": "error", - "wrap-iife": ["error", "inside"], - "no-shadow": ["error", { builtinGlobals: true, hoist: "never" }], - "no-use-before-define": "off", - "no-var": "error", - "prefer-numeric-literals": "error", - "prefer-const": "warn", - "prefer-arrow-callback": "error", - "prefer-rest-params": "error", - "prefer-spread": "error", - "prefer-template": "error", - }, -}; diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index 785efb8..0000000 --- a/.prettierignore +++ /dev/null @@ -1,5 +0,0 @@ -node_modules -coverage -dist -es -lib diff --git a/.prettierrc.json b/.prettierrc.json deleted file mode 100644 index 627187b..0000000 --- a/.prettierrc.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "printWidth": 120, - "useTabs": true, - "semi": true, - "singleQuote": false, - "jsxSingleQuote": false -} diff --git a/babel.config.js b/babel.config.js deleted file mode 100644 index 815eb05..0000000 --- a/babel.config.js +++ /dev/null @@ -1,100 +0,0 @@ -module.exports = function (api) { - api.cache.never(); - - const target = process.env.TARGET || "cjs"; - - const presets = []; - const plugins = [ - "@babel/plugin-syntax-dynamic-import", - "@babel/plugin-syntax-import-meta", - "@babel/plugin-proposal-class-properties", - "@babel/plugin-proposal-export-namespace-from", - "@babel/plugin-proposal-numeric-separator", - "@babel/plugin-proposal-throw-expressions", - "@babel/plugin-proposal-export-default-from", - "@babel/plugin-proposal-logical-assignment-operators", - "@babel/plugin-proposal-optional-chaining", - "@babel/plugin-proposal-nullish-coalescing-operator", - ]; - - const preset = [ - "@babel/preset-env", - { - modules: "commonjs", // transpile modules into common-js syntax by default - targets: {}, - }, - ]; - - const runtime = [ - "@babel/plugin-transform-runtime", - { - absoluteRuntime: true, - regenerator: false, - useESModules: false, // don't output es-modules by default - corejs: false, - helpers: true, - }, - ]; - - switch (target) { - case "browser": - Object.assign(preset[1], { - targets: { - ie: "10", - }, - useBuiltIns: "usage", - corejs: 3, - exclude: [ - /es\.array\.(?!(find$)).*/, - "es.array-buffer.*", - "es.function.*", - "es.json.*", - /es\.math\.(?!(imul$)).*/, - "es.map.*", - /es\.object\.(?!(assign$|entries$)).*/, - "es.promise.*", - "es.regexp.*", - "es.reflect.*", - "es.set.*", - "es.string.*", - "es.symbol.*", - /es\.typed-array\.(?!(from$|of)).*/, - "es.weak-map.*", - "web.*", - ], - }); - break; - - case "cjs": - Object.assign(preset[1], { - targets: { - node: "6", - }, - useBuiltIns: "usage", - corejs: 3, - }); - break; - - case "es": - Object.assign(runtime[1], { - useESModules: true, - }); - Object.assign(preset[1], { - modules: false, - targets: { - node: "13.20", - }, - }); - break; - default: - throw new Error(`Unsupported target '${target}'`); - } - - presets.push(preset); - plugins.push(runtime); - - return { - presets, - plugins, - }; -}; diff --git a/jest.config.js b/jest.config.js deleted file mode 100644 index b4c18e5..0000000 --- a/jest.config.js +++ /dev/null @@ -1,10 +0,0 @@ -/** @type {import('ts-jest').JestConfigWithTsJest} */ -module.exports = { - clearMocks: true, - coverageDirectory: "coverage", - testEnvironment: "node", - testRegex: "/__tests__/.*\\.(test|spec)\\.[t|j]sx?$", - transform: { - "^.+\\.[t|j]sx?$": ["ts-jest"], - }, -}; diff --git a/package-lock.json b/package-lock.json index 163ba5d..e585c7f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16592 +1,2900 @@ { - "name": "@absmartly/javascript-sdk", - "version": "1.13.2", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "@absmartly/javascript-sdk", - "version": "1.13.2", - "license": "Apache-2.0", - "dependencies": { - "core-js": "^3.20.0", - "node-fetch": "^2.6.7", - "rfdc": "^1.3.0" - }, - "devDependencies": { - "@babel/cli": "^7.17.3", - "@babel/core": "^7.17.4", - "@babel/eslint-parser": "^7.17.0", - "@babel/plugin-proposal-class-properties": "^7.16.7", - "@babel/plugin-proposal-export-default-from": "^7.16.7", - "@babel/plugin-proposal-export-namespace-from": "^7.16.7", - "@babel/plugin-proposal-logical-assignment-operators": "^7.16.7", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.7", - "@babel/plugin-proposal-numeric-separator": "^7.16.7", - "@babel/plugin-proposal-optional-chaining": "^7.16.7", - "@babel/plugin-proposal-throw-expressions": "^7.16.7", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-transform-runtime": "^7.16.7", - "@babel/preset-env": "^7.16.11", - "@babel/preset-typescript": "^7.18.6", - "@babel/register": "^7.17.0", - "@types/jest": "^29.2.5", - "@types/node-fetch": "^2.6.2", - "@typescript-eslint/eslint-plugin": "^5.48.2", - "@typescript-eslint/parser": "^5.48.2", - "babel-jest": "^29", - "babel-loader": "^8.2.3", - "eslint": "^7.32.0", - "eslint-config-prettier": "^7.1.0", - "jest": "^29.3.1", - "prettier": "^2.4.1", - "terser-webpack-plugin": "^5.3.1", - "ts-jest": "^29.0.5", - "ts-loader": "^9.4.2", - "typescript": "^4.9.4", - "webpack": "^5.60.0", - "webpack-bundle-analyzer": "^4.5.0", - "webpack-cli": "^4.9.1" - }, - "engines": { - "node": ">=6", - "npm": ">=3" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", - "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", - "dev": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.1.0", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/cli": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.20.7.tgz", - "integrity": "sha512-WylgcELHB66WwQqItxNILsMlaTd8/SO6SgTTjMp4uCI7P4QyH1r3nqgFmO3BfM4AtfniHgFMH3EpYFj/zynBkQ==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.8", - "commander": "^4.0.1", - "convert-source-map": "^1.1.0", - "fs-readdir-recursive": "^1.1.0", - "glob": "^7.2.0", - "make-dir": "^2.1.0", - "slash": "^2.0.0" - }, - "bin": { - "babel": "bin/babel.js", - "babel-external-helpers": "bin/babel-external-helpers.js" - }, - "engines": { - "node": ">=6.9.0" - }, - "optionalDependencies": { - "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents.3", - "chokidar": "^3.4.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.4.tgz", - "integrity": "sha512-r1IONyb6Ia+jYR2vvIDhdWdlTGhqbBoFqLTQidzZ4kepUFH15ejXvFHxCVbtl7BOXIudsIubf4E81xeA3h3IXA==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.23.4", - "chalk": "^2.4.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.20.10", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.10.tgz", - "integrity": "sha512-sEnuDPpOJR/fcafHMjpcpGN5M2jbUGUHwmuWKM/YdPzeEDJg8bgmbcWQFUfE32MQjti1koACvoPVsDe8Uq+idg==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.7.tgz", - "integrity": "sha512-t1ZjCluspe5DW24bn2Rr1CDb2v9rn/hROtg9a2tmd0+QYf4bsloYfLQzjG4qHPNMhWtKdGC33R5AxGR2Af2cBw==", - "dev": true, - "dependencies": { - "@ampproject/remapping": "^2.1.0", - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.20.7", - "@babel/helper-compilation-targets": "^7.20.7", - "@babel/helper-module-transforms": "^7.20.7", - "@babel/helpers": "^7.20.7", - "@babel/parser": "^7.20.7", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.20.7", - "@babel/types": "^7.20.7", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.1", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/eslint-parser": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.19.1.tgz", - "integrity": "sha512-AqNf2QWt1rtu2/1rLswy6CDP7H9Oh3mMhk177Y67Rg8d7RD9WfOLLv8CGn6tisFvS2htm86yIe1yLF6I1UDaGQ==", - "dev": true, - "dependencies": { - "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", - "eslint-visitor-keys": "^2.1.0", - "semver": "^6.3.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || >=14.0.0" - }, - "peerDependencies": { - "@babel/core": ">=7.11.0", - "eslint": "^7.5.0 || ^8.0.0" - } - }, - "node_modules/@babel/generator": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.4.tgz", - "integrity": "sha512-esuS49Cga3HcThFNebGhlgsrVLkvhqvYDTzgjfFFlHJcIfLe5jFmRRfCQ1KuBfc4Jrtn3ndLgKWAKjBE+IraYQ==", - "dev": true, - "dependencies": { - "@babel/types": "^7.23.4", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", - "jsesc": "^2.5.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dev": true, - "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", - "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz", - "integrity": "sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==", - "dev": true, - "dependencies": { - "@babel/helper-explode-assignable-expression": "^7.18.6", - "@babel/types": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz", - "integrity": "sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.20.5", - "@babel/helper-validator-option": "^7.18.6", - "browserslist": "^4.21.3", - "lru-cache": "^5.1.1", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.20.7.tgz", - "integrity": "sha512-LtoWbDXOaidEf50hmdDqn9g8VEzsorMexoWMQdQODbvmqYmaF23pBP5VNPAGIFHsFQCIeKokDiz3CH5Y2jlY6w==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-member-expression-to-functions": "^7.20.7", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/helper-replace-supers": "^7.20.7", - "@babel/helper-split-export-declaration": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.20.5.tgz", - "integrity": "sha512-m68B1lkg3XDGX5yCvGO0kPx3v9WIYLnzjKfPcQiwntEQa5ZeRkPmo2X/ISJc8qxWGfwUr+kvZAeEzAwLec2r2w==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "regexpu-core": "^5.2.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz", - "integrity": "sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==", - "dev": true, - "dependencies": { - "@babel/helper-compilation-targets": "^7.17.7", - "@babel/helper-plugin-utils": "^7.16.7", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2", - "semver": "^6.1.2" - }, - "peerDependencies": { - "@babel/core": "^7.4.0-0" - } - }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-explode-assignable-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz", - "integrity": "sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", - "dev": true, - "dependencies": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.20.7.tgz", - "integrity": "sha512-9J0CxJLq315fEdi4s7xK5TQaNYjZw+nDVpVqr1axNGKzdrdwYBD5b4uKv3n75aABG0rCCTK8Im8Ww7eYfMrZgw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.20.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", - "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.20.11", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.11.tgz", - "integrity": "sha512-uRy78kN4psmji1s2QtbtcCSaj/LILFDp0f/ymhpQH5QY3nljUZCaNWz9X1dEj/8MBdBEFECs7yRhKn8i7NjZgg==", - "dev": true, - "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-simple-access": "^7.20.2", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/helper-validator-identifier": "^7.19.1", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.20.10", - "@babel/types": "^7.20.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", - "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz", - "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz", - "integrity": "sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-wrap-function": "^7.18.9", - "@babel/types": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.20.7.tgz", - "integrity": "sha512-vujDMtB6LVfNW13jhlCrp48QNslK6JXi7lQG736HVbHz/mbf4Dc7tIRh1Xf5C0rF7BP8iiSxGMCmY6Ci1ven3A==", - "dev": true, - "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-member-expression-to-functions": "^7.20.7", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.20.7", - "@babel/types": "^7.20.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-simple-access": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz", - "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.20.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz", - "integrity": "sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.20.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", - "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", - "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-wrap-function": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.20.5.tgz", - "integrity": "sha512-bYMxIWK5mh+TgXGVqAtnu5Yn1un+v8DDZtqyzKRLUzrh70Eal2O3aZ7aPYiMADO4uKlkzOiRiZ6GX5q3qxvW9Q==", - "dev": true, - "dependencies": { - "@babel/helper-function-name": "^7.19.0", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.20.5", - "@babel/types": "^7.20.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.7.tgz", - "integrity": "sha512-PBPjs5BppzsGaxHQCDKnZ6Gd9s6xl8bBCluz3vEInLGRJmnZan4F6BYCeqtyXqkk4W5IlPmjK4JlOuZkpJ3xZA==", - "dev": true, - "dependencies": { - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.20.7", - "@babel/types": "^7.20.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", - "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.4.tgz", - "integrity": "sha512-vf3Xna6UEprW+7t6EtOmFpHNAuxw3xqPZghy+brsnusscJRW5BMUzzHZc5ICjULee81WeUV2jjakG09MDglJXQ==", - "dev": true, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz", - "integrity": "sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.20.7.tgz", - "integrity": "sha512-sbr9+wNE5aXMBBFBICk01tt7sBf2Oc9ikRFEcem/ZORup9IMUdNhW7/wVLEbbtlWOsEubJet46mHAL2C8+2jKQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", - "@babel/plugin-proposal-optional-chaining": "^7.20.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.13.0" - } - }, - "node_modules/@babel/plugin-proposal-async-generator-functions": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz", - "integrity": "sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==", - "dev": true, - "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-remap-async-to-generator": "^7.18.9", - "@babel/plugin-syntax-async-generators": "^7.8.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-class-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", - "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-class-static-block": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.20.7.tgz", - "integrity": "sha512-AveGOoi9DAjUYYuUAG//Ig69GlazLnoyzMw68VCDux+c1tsnnH/OkYcpz/5xzMkEFC6UxjR5Gw1c+iY2wOGVeQ==", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.20.7", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-class-static-block": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0" - } - }, - "node_modules/@babel/plugin-proposal-dynamic-import": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz", - "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-export-default-from": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.18.10.tgz", - "integrity": "sha512-5H2N3R2aQFxkV4PIBUR/i7PUSwgTZjouJKzI8eKswfIjT0PhvzkPn0t0wIS5zn6maQuvtT0t1oHtMUz61LOuow==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-export-default-from": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-export-namespace-from": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", - "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-json-strings": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz", - "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-json-strings": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.20.7.tgz", - "integrity": "sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", - "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-numeric-separator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", - "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-object-rest-spread": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz", - "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.20.5", - "@babel/helper-compilation-targets": "^7.20.7", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.20.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-optional-catch-binding": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", - "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-optional-chaining": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.20.7.tgz", - "integrity": "sha512-T+A7b1kfjtRM51ssoOfS1+wbyCVqorfyZhT99TvxxLMirPShD8CzKMRepMlCBGM5RpHMbn8s+5MMHnPstJH6mQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-private-methods": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", - "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.20.5.tgz", - "integrity": "sha512-Vq7b9dUA12ByzB4EjQTPo25sFhY+08pQDBSZRtUAkj7lb7jahaHR5igera16QZ+3my1nYR4dKsNdYj5IjPHilQ==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-create-class-features-plugin": "^7.20.5", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-throw-expressions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-throw-expressions/-/plugin-proposal-throw-expressions-7.18.6.tgz", - "integrity": "sha512-WHOrJyhGoGrdtW480L79cF7Iq/gZDZ/z6OqK7mVyFR5I37dTpog/wNgb6hmaM3HYZtULEJl++7VaMWkNZsOcHg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-throw-expressions": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-unicode-property-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", - "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-export-default-from": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.18.6.tgz", - "integrity": "sha512-Kr//z3ujSVNx6E9z9ih5xXXMqK07VVTuqPmqGe6Mss/zW5XPeLZeSDZoP9ab/hT4wPKqAgjl2PnhPrcpk8Seew==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz", - "integrity": "sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.19.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", - "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-throw-expressions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-throw-expressions/-/plugin-syntax-throw-expressions-7.18.6.tgz", - "integrity": "sha512-rp1CqEZXGv1z1YZ3qYffBH3rhnOxrTwQG8fh2yqulTurwv9zu3Gthfd+niZBLSOi1rY6146TgF+JmVeDXaX4TQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.20.0.tgz", - "integrity": "sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.19.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.20.7.tgz", - "integrity": "sha512-3poA5E7dzDomxj9WXWwuD6A5F3kc7VXwIJO+E+J8qtDtS+pXPAhrgEyh+9GBwBgPq1Z+bB+/JD60lp5jsN7JPQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.20.7.tgz", - "integrity": "sha512-Uo5gwHPT9vgnSXQxqGtpdufUiWp96gk7yiP4Mp5bm1QMkEmLXBO7PAGYbKoJ6DhAwiNkcHFBol/x5zZZkL/t0Q==", - "dev": true, - "dependencies": { - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-remap-async-to-generator": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz", - "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.20.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.11.tgz", - "integrity": "sha512-tA4N427a7fjf1P0/2I4ScsHGc5jcHPbb30xMbaTke2gxDuWpUfXDuX1FEymJwKk4tuGUvGcejAR6HdZVqmmPyw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.20.7.tgz", - "integrity": "sha512-LWYbsiXTPKl+oBlXUGlwNlJZetXD5Am+CyBdqhPsDVjM9Jc8jwBJFrKhHf900Kfk2eZG1y9MAG3UNajol7A4VQ==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-compilation-targets": "^7.20.7", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-replace-supers": "^7.20.7", - "@babel/helper-split-export-declaration": "^7.18.6", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.20.7.tgz", - "integrity": "sha512-Lz7MvBK6DTjElHAmfu6bfANzKcxpyNPeYBGEafyA6E5HtRpjpZwU+u7Qrgz/2OR0z+5TvKYbPdphfSaAcZBrYQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/template": "^7.20.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.7.tgz", - "integrity": "sha512-Xwg403sRrZb81IVB79ZPqNQME23yhugYVqgTxAhT99h485F4f+GMELFhhOsscDUB7HCswepKeCKLn/GZvUKoBA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz", - "integrity": "sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz", - "integrity": "sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz", - "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==", - "dev": true, - "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-for-of": { - "version": "7.18.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz", - "integrity": "sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-function-name": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz", - "integrity": "sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==", - "dev": true, - "dependencies": { - "@babel/helper-compilation-targets": "^7.18.9", - "@babel/helper-function-name": "^7.18.9", - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-literals": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz", - "integrity": "sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz", - "integrity": "sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.20.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.20.11.tgz", - "integrity": "sha512-NuzCt5IIYOW0O30UvqktzHYR2ud5bOWbY0yaxWZ6G+aFzOMJvrs5YHNikrbdaT15+KNO31nPOy5Fim3ku6Zb5g==", - "dev": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.20.11", - "@babel/helper-plugin-utils": "^7.20.2" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.20.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.20.11.tgz", - "integrity": "sha512-S8e1f7WQ7cimJQ51JkAaDrEtohVEitXjgCGAS2N8S31Y42E+kWwfSz83LYz57QdBm7q9diARVqanIaH2oVgQnw==", - "dev": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.20.11", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-simple-access": "^7.20.2" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.20.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.20.11.tgz", - "integrity": "sha512-vVu5g9BPQKSFEmvt2TA4Da5N+QVS66EX21d8uoOihC+OCpUoGvzVsXeqFdtAEfVa5BILAeFt+U7yVmLbQnAJmw==", - "dev": true, - "dependencies": { - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-module-transforms": "^7.20.11", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-validator-identifier": "^7.19.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz", - "integrity": "sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==", - "dev": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.20.5.tgz", - "integrity": "sha512-mOW4tTzi5iTLnw+78iEq3gr8Aoq4WNRGpmSlrogqaiCBoR1HFhpU4JkpQFOHfeYx3ReVIFWOQJS4aZBRvuZ6mA==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.20.5", - "@babel/helper-plugin-utils": "^7.20.2" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-new-target": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz", - "integrity": "sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-object-super": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz", - "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-replace-supers": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-parameters": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.20.7.tgz", - "integrity": "sha512-WiWBIkeHKVOSYPO0pWkxGPfKeWrCJyD3NJ53+Lrp/QMSZbsVPovrVl2aWZ19D/LTVnaDv5Ap7GJ/B2CTOZdrfA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz", - "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.20.5.tgz", - "integrity": "sha512-kW/oO7HPBtntbsahzQ0qSE3tFvkFwnbozz3NWFhLGqH75vLEg+sCGngLlhVkePlCs3Jv0dBBHDzCHxNiFAQKCQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "regenerator-transform": "^0.15.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz", - "integrity": "sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-runtime": { - "version": "7.19.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.19.6.tgz", - "integrity": "sha512-PRH37lz4JU156lYFW1p8OxE5i7d6Sl/zV58ooyr+q1J1lnQPyg5tIiXlIwNVhJaY4W3TmOtdc8jqdXQcB1v5Yw==", - "dev": true, - "dependencies": { - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.19.0", - "babel-plugin-polyfill-corejs2": "^0.3.3", - "babel-plugin-polyfill-corejs3": "^0.6.0", - "babel-plugin-polyfill-regenerator": "^0.4.1", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz", - "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-spread": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.20.7.tgz", - "integrity": "sha512-ewBbHQ+1U/VnH1fxltbJqDeWBU1oNLG8Dj11uIv3xVf7nrQu0bPGe5Rf716r7K5Qz+SqtAOVswoVunoiBtGhxw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz", - "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz", - "integrity": "sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz", - "integrity": "sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-typescript": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.20.7.tgz", - "integrity": "sha512-m3wVKEvf6SoszD8pu4NZz3PvfKRCMgk6D6d0Qi9hNnlM5M6CFS92EgF4EiHVLKbU0r/r7ty1hg7NPZwE7WRbYw==", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.20.7", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-typescript": "^7.20.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz", - "integrity": "sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz", - "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.20.2.tgz", - "integrity": "sha512-1G0efQEWR1EHkKvKHqbG+IN/QdgwfByUpM5V5QroDzGV2t3S/WXNQd693cHiHTlCFMpr9B6FkPFXDA2lQcKoDg==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.20.1", - "@babel/helper-compilation-targets": "^7.20.0", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-validator-option": "^7.18.6", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.18.9", - "@babel/plugin-proposal-async-generator-functions": "^7.20.1", - "@babel/plugin-proposal-class-properties": "^7.18.6", - "@babel/plugin-proposal-class-static-block": "^7.18.6", - "@babel/plugin-proposal-dynamic-import": "^7.18.6", - "@babel/plugin-proposal-export-namespace-from": "^7.18.9", - "@babel/plugin-proposal-json-strings": "^7.18.6", - "@babel/plugin-proposal-logical-assignment-operators": "^7.18.9", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", - "@babel/plugin-proposal-numeric-separator": "^7.18.6", - "@babel/plugin-proposal-object-rest-spread": "^7.20.2", - "@babel/plugin-proposal-optional-catch-binding": "^7.18.6", - "@babel/plugin-proposal-optional-chaining": "^7.18.9", - "@babel/plugin-proposal-private-methods": "^7.18.6", - "@babel/plugin-proposal-private-property-in-object": "^7.18.6", - "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.20.0", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-transform-arrow-functions": "^7.18.6", - "@babel/plugin-transform-async-to-generator": "^7.18.6", - "@babel/plugin-transform-block-scoped-functions": "^7.18.6", - "@babel/plugin-transform-block-scoping": "^7.20.2", - "@babel/plugin-transform-classes": "^7.20.2", - "@babel/plugin-transform-computed-properties": "^7.18.9", - "@babel/plugin-transform-destructuring": "^7.20.2", - "@babel/plugin-transform-dotall-regex": "^7.18.6", - "@babel/plugin-transform-duplicate-keys": "^7.18.9", - "@babel/plugin-transform-exponentiation-operator": "^7.18.6", - "@babel/plugin-transform-for-of": "^7.18.8", - "@babel/plugin-transform-function-name": "^7.18.9", - "@babel/plugin-transform-literals": "^7.18.9", - "@babel/plugin-transform-member-expression-literals": "^7.18.6", - "@babel/plugin-transform-modules-amd": "^7.19.6", - "@babel/plugin-transform-modules-commonjs": "^7.19.6", - "@babel/plugin-transform-modules-systemjs": "^7.19.6", - "@babel/plugin-transform-modules-umd": "^7.18.6", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.19.1", - "@babel/plugin-transform-new-target": "^7.18.6", - "@babel/plugin-transform-object-super": "^7.18.6", - "@babel/plugin-transform-parameters": "^7.20.1", - "@babel/plugin-transform-property-literals": "^7.18.6", - "@babel/plugin-transform-regenerator": "^7.18.6", - "@babel/plugin-transform-reserved-words": "^7.18.6", - "@babel/plugin-transform-shorthand-properties": "^7.18.6", - "@babel/plugin-transform-spread": "^7.19.0", - "@babel/plugin-transform-sticky-regex": "^7.18.6", - "@babel/plugin-transform-template-literals": "^7.18.9", - "@babel/plugin-transform-typeof-symbol": "^7.18.9", - "@babel/plugin-transform-unicode-escapes": "^7.18.10", - "@babel/plugin-transform-unicode-regex": "^7.18.6", - "@babel/preset-modules": "^0.1.5", - "@babel/types": "^7.20.2", - "babel-plugin-polyfill-corejs2": "^0.3.3", - "babel-plugin-polyfill-corejs3": "^0.6.0", - "babel-plugin-polyfill-regenerator": "^0.4.1", - "core-js-compat": "^3.25.1", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-modules": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", - "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", - "@babel/plugin-transform-dotall-regex": "^7.4.4", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-typescript": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.18.6.tgz", - "integrity": "sha512-s9ik86kXBAnD760aybBucdpnLsAt0jK1xqJn2juOn9lkOvSHV60os5hxoVJsPzMQxvnUJFAlkont2DvvaYEBtQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-validator-option": "^7.18.6", - "@babel/plugin-transform-typescript": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/register": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.18.9.tgz", - "integrity": "sha512-ZlbnXDcNYHMR25ITwwNKT88JiaukkdVj/nG7r3wnuXkOTHc60Uy05PwMCPre0hSkY68E6zK3xz+vUJSP2jWmcw==", - "dev": true, - "dependencies": { - "clone-deep": "^4.0.1", - "find-cache-dir": "^2.0.0", - "make-dir": "^2.1.0", - "pirates": "^4.0.5", - "source-map-support": "^0.5.16" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz", - "integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==", - "dev": true, - "dependencies": { - "regenerator-runtime": "^0.13.11" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.4.tgz", - "integrity": "sha512-IYM8wSUwunWTB6tFC2dkKZhxbIjHoWemdK+3f8/wq8aKhbUscxD5MX72ubd90fxvFknaLPeGw5ycU84V1obHJg==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.23.4", - "@babel/generator": "^7.23.4", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.4", - "@babel/types": "^7.23.4", - "debug": "^4.1.0", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.4.tgz", - "integrity": "sha512-7uIFwVYpoplT5jp/kVv6EF93VaJ8H+Yn5IczYiaAi98ajzjfoZfslet/e0sLh+wVBjb2qqIut1b0S26VSafsSQ==", - "dev": true, - "dependencies": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true - }, - "node_modules/@discoveryjs/json-ext": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", - "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", - "dev": true, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", - "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", - "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", - "globals": "^13.9.0", - "ignore": "^4.0.6", - "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.19.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", - "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@eslint/eslintrc/node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/@eslint/eslintrc/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", - "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", - "dev": true, - "dependencies": { - "@humanwhocodes/object-schema": "^1.2.0", - "debug": "^4.1.1", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true - }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.3.1.tgz", - "integrity": "sha512-IRE6GD47KwcqA09RIWrabKdHPiKDGgtAL31xDxbi/RjQMsr+lY+ppxmHwY0dUEV3qvvxZzoe5Hl0RXZJOjQNUg==", - "dev": true, - "dependencies": { - "@jest/types": "^29.3.1", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.3.1", - "jest-util": "^29.3.1", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/console/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==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/console/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/console/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==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/console/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/@jest/console/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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console/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==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/core": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.3.1.tgz", - "integrity": "sha512-0ohVjjRex985w5MmO5L3u5GR1O30DexhBSpuwx2P+9ftyqHdJXnk7IUWiP80oHMvt7ubHCJHxV0a0vlKVuZirw==", - "dev": true, - "dependencies": { - "@jest/console": "^29.3.1", - "@jest/reporters": "^29.3.1", - "@jest/test-result": "^29.3.1", - "@jest/transform": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.2.0", - "jest-config": "^29.3.1", - "jest-haste-map": "^29.3.1", - "jest-message-util": "^29.3.1", - "jest-regex-util": "^29.2.0", - "jest-resolve": "^29.3.1", - "jest-resolve-dependencies": "^29.3.1", - "jest-runner": "^29.3.1", - "jest-runtime": "^29.3.1", - "jest-snapshot": "^29.3.1", - "jest-util": "^29.3.1", - "jest-validate": "^29.3.1", - "jest-watcher": "^29.3.1", - "micromatch": "^4.0.4", - "pretty-format": "^29.3.1", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/core/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==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/core/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/core/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==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/core/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/@jest/core/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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/core/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/core/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==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/environment": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.3.1.tgz", - "integrity": "sha512-pMmvfOPmoa1c1QpfFW0nXYtNLpofqo4BrCIk6f2kW4JFeNlHV2t3vd+3iDLf31e2ot2Mec0uqZfmI+U0K2CFag==", - "dev": true, - "dependencies": { - "@jest/fake-timers": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/node": "*", - "jest-mock": "^29.3.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/expect": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.3.1.tgz", - "integrity": "sha512-QivM7GlSHSsIAWzgfyP8dgeExPRZ9BIe2LsdPyEhCGkZkoyA+kGsoIzbKAfZCvvRzfZioKwPtCZIt5SaoxYCvg==", - "dev": true, - "dependencies": { - "expect": "^29.3.1", - "jest-snapshot": "^29.3.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/expect-utils": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.3.1.tgz", - "integrity": "sha512-wlrznINZI5sMjwvUoLVk617ll/UYfGIZNxmbU+Pa7wmkL4vYzhV9R2pwVqUh4NWWuLQWkI8+8mOkxs//prKQ3g==", - "dev": true, - "dependencies": { - "jest-get-type": "^29.2.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/fake-timers": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.3.1.tgz", - "integrity": "sha512-iHTL/XpnDlFki9Tq0Q1GGuVeQ8BHZGIYsvCO5eN/O/oJaRzofG9Xndd9HuSDBI/0ZS79pg0iwn07OMTQ7ngF2A==", - "dev": true, - "dependencies": { - "@jest/types": "^29.3.1", - "@sinonjs/fake-timers": "^9.1.2", - "@types/node": "*", - "jest-message-util": "^29.3.1", - "jest-mock": "^29.3.1", - "jest-util": "^29.3.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/globals": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.3.1.tgz", - "integrity": "sha512-cTicd134vOcwO59OPaB6AmdHQMCtWOe+/DitpTZVxWgMJ+YvXL1HNAmPyiGbSHmF/mXVBkvlm8YYtQhyHPnV6Q==", - "dev": true, - "dependencies": { - "@jest/environment": "^29.3.1", - "@jest/expect": "^29.3.1", - "@jest/types": "^29.3.1", - "jest-mock": "^29.3.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/reporters": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.3.1.tgz", - "integrity": "sha512-GhBu3YFuDrcAYW/UESz1JphEAbvUjaY2vShRZRoRY1mxpCMB3yGSJ4j9n0GxVlEOdCf7qjvUfBCrTUUqhVfbRA==", - "dev": true, - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.3.1", - "@jest/test-result": "^29.3.1", - "@jest/transform": "^29.3.1", - "@jest/types": "^29.3.1", - "@jridgewell/trace-mapping": "^0.3.15", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^5.1.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.3.1", - "jest-util": "^29.3.1", - "jest-worker": "^29.3.1", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "v8-to-istanbul": "^9.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/reporters/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==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/reporters/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/reporters/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==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/reporters/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/@jest/reporters/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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/reporters/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/reporters/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==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/schemas": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", - "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", - "dev": true, - "dependencies": { - "@sinclair/typebox": "^0.24.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/source-map": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.2.0.tgz", - "integrity": "sha512-1NX9/7zzI0nqa6+kgpSdKPK+WU1p+SJk3TloWZf5MzPbxri9UEeXX5bWZAPCzbQcyuAzubcdUHA7hcNznmRqWQ==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.15", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/test-result": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.3.1.tgz", - "integrity": "sha512-qeLa6qc0ddB0kuOZyZIhfN5q0e2htngokyTWsGriedsDhItisW7SDYZ7ceOe57Ii03sL988/03wAcBh3TChMGw==", - "dev": true, - "dependencies": { - "@jest/console": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/test-sequencer": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.3.1.tgz", - "integrity": "sha512-IqYvLbieTv20ArgKoAMyhLHNrVHJfzO6ARZAbQRlY4UGWfdDnLlZEF0BvKOMd77uIiIjSZRwq3Jb3Fa3I8+2UA==", - "dev": true, - "dependencies": { - "@jest/test-result": "^29.3.1", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.3.1", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/test-sequencer/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/transform": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.3.1.tgz", - "integrity": "sha512-8wmCFBTVGYqFNLWfcOWoVuMuKYPUBTnTMDkdvFtAYELwDOl9RGwOsvQWGPFxDJ8AWY9xM/8xCXdqmPK3+Q5Lug==", - "dev": true, - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.3.1", - "@jridgewell/trace-mapping": "^0.3.15", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.3.1", - "jest-regex-util": "^29.2.0", - "jest-util": "^29.3.1", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/transform/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==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/transform/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/transform/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==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/transform/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/@jest/transform/node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true - }, - "node_modules/@jest/transform/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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/transform/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/transform/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==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/types": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.3.1.tgz", - "integrity": "sha512-d0S0jmmTpjnhCmNpApgX3jrUZgZ22ivKJRvL2lli5hpCRoNnp1f85r2/wpKfXuYu8E7Jjh1hGfhPyup1NM5AmA==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.0.0", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/types/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==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/types/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/types/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==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/types/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/@jest/types/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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/types/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==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", - "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", - "dev": true, - "dependencies": { - "@jridgewell/set-array": "^1.0.0", - "@jridgewell/sourcemap-codec": "^1.4.10" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", - "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", - "dev": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "node_modules/@jridgewell/source-map/node_modules/@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dev": true, - "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.17", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", - "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", - "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" - } - }, - "node_modules/@nicolo-ribaudo/chokidar-2": { - "version": "2.1.8-no-fsevents.3", - "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz", - "integrity": "sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==", - "dev": true, - "optional": true - }, - "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { - "version": "5.1.1-v1", - "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", - "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", - "dev": true, - "dependencies": { - "eslint-scope": "5.1.1" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@polka/url": { - "version": "1.0.0-next.21", - "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz", - "integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==", - "dev": true - }, - "node_modules/@sinclair/typebox": { - "version": "0.24.51", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", - "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==", - "dev": true - }, - "node_modules/@sinonjs/commons": { - "version": "1.8.6", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", - "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", - "dev": true, - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", - "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^1.7.0" - } - }, - "node_modules/@types/babel__core": { - "version": "7.1.20", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.20.tgz", - "integrity": "sha512-PVb6Bg2QuscZ30FvOU7z4guG6c926D9YRvOxEaelzndpMsvP+YM74Q/dAFASpg2l6+XLalxSGxcq/lrgYWZtyQ==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.6.4", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", - "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", - "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.3.tgz", - "integrity": "sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==", - "dev": true, - "dependencies": { - "@babel/types": "^7.3.0" - } - }, - "node_modules/@types/eslint": { - "version": "8.4.10", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.10.tgz", - "integrity": "sha512-Sl/HOqN8NKPmhWo2VBEPm0nvHnu2LL3v9vKo8MEq0EtbJ4eVzGPl41VNPvn5E1i5poMk4/XD8UriLHpJvEP/Nw==", - "dev": true, - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint-scope": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", - "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", - "dev": true, - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, - "node_modules/@types/estree": { - "version": "0.0.51", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", - "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", - "dev": true - }, - "node_modules/@types/graceful-fs": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz", - "integrity": "sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", - "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", - "dev": true - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/jest": { - "version": "29.2.5", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.2.5.tgz", - "integrity": "sha512-H2cSxkKgVmqNHXP7TC2L/WUorrZu8ZigyRywfVzv6EyBlxj39n4C00hjXYQWsbwqgElaj/CiAeSRmk5GoaKTgw==", - "dev": true, - "dependencies": { - "expect": "^29.0.0", - "pretty-format": "^29.0.0" - } - }, - "node_modules/@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", - "dev": true - }, - "node_modules/@types/node": { - "version": "18.11.18", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", - "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==", - "dev": true - }, - "node_modules/@types/node-fetch": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.2.tgz", - "integrity": "sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A==", - "dev": true, - "dependencies": { - "@types/node": "*", - "form-data": "^3.0.0" - } - }, - "node_modules/@types/prettier": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz", - "integrity": "sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==", - "dev": true - }, - "node_modules/@types/semver": { - "version": "7.3.13", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", - "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", - "dev": true - }, - "node_modules/@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", - "dev": true - }, - "node_modules/@types/yargs": { - "version": "17.0.19", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.19.tgz", - "integrity": "sha512-cAx3qamwaYX9R0fzOIZAlFpo4A+1uBVCxqpKz9D26uTF4srRXaGTTsikQmaotCtNdbhzyUH7ft6p9ktz9s6UNQ==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", - "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", - "dev": true - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.48.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.48.2.tgz", - "integrity": "sha512-sR0Gja9Ky1teIq4qJOl0nC+Tk64/uYdX+mi+5iB//MH8gwyx8e3SOyhEzeLZEFEEfCaLf8KJq+Bd/6je1t+CAg==", - "dev": true, - "dependencies": { - "@typescript-eslint/scope-manager": "5.48.2", - "@typescript-eslint/type-utils": "5.48.2", - "@typescript-eslint/utils": "5.48.2", - "debug": "^4.3.4", - "ignore": "^5.2.0", - "natural-compare-lite": "^1.4.0", - "regexpp": "^3.2.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^5.0.0", - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/@typescript-eslint/parser": { - "version": "5.48.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.48.2.tgz", - "integrity": "sha512-38zMsKsG2sIuM5Oi/olurGwYJXzmtdsHhn5mI/pQogP+BjYVkK5iRazCQ8RGS0V+YLk282uWElN70zAAUmaYHw==", - "dev": true, - "dependencies": { - "@typescript-eslint/scope-manager": "5.48.2", - "@typescript-eslint/types": "5.48.2", - "@typescript-eslint/typescript-estree": "5.48.2", - "debug": "^4.3.4" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "5.48.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.48.2.tgz", - "integrity": "sha512-zEUFfonQid5KRDKoI3O+uP1GnrFd4tIHlvs+sTJXiWuypUWMuDaottkJuR612wQfOkjYbsaskSIURV9xo4f+Fw==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.48.2", - "@typescript-eslint/visitor-keys": "5.48.2" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "5.48.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.48.2.tgz", - "integrity": "sha512-QVWx7J5sPMRiOMJp5dYshPxABRoZV1xbRirqSk8yuIIsu0nvMTZesKErEA3Oix1k+uvsk8Cs8TGJ6kQ0ndAcew==", - "dev": true, - "dependencies": { - "@typescript-eslint/typescript-estree": "5.48.2", - "@typescript-eslint/utils": "5.48.2", - "debug": "^4.3.4", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/types": { - "version": "5.48.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.48.2.tgz", - "integrity": "sha512-hE7dA77xxu7ByBc6KCzikgfRyBCTst6dZQpwaTy25iMYOnbNljDT4hjhrGEJJ0QoMjrfqrx+j1l1B9/LtKeuqA==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.48.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.48.2.tgz", - "integrity": "sha512-bibvD3z6ilnoVxUBFEgkO0k0aFvUc4Cttt0dAreEr+nrAHhWzkO83PEVVuieK3DqcgL6VAK5dkzK8XUVja5Zcg==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.48.2", - "@typescript-eslint/visitor-keys": "5.48.2", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/@typescript-eslint/utils": { - "version": "5.48.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.48.2.tgz", - "integrity": "sha512-2h18c0d7jgkw6tdKTlNaM7wyopbLRBiit8oAxoP89YnuBOzCZ8g8aBCaCqq7h208qUTroL7Whgzam7UY3HVLow==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.48.2", - "@typescript-eslint/types": "5.48.2", - "@typescript-eslint/typescript-estree": "5.48.2", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0", - "semver": "^7.3.7" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.48.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.48.2.tgz", - "integrity": "sha512-z9njZLSkwmjFWUelGEwEbdf4NwKvfHxvGC0OcGN1Hp/XNDIcJ7D5DpPNPv6x6/mFvc1tQHsaWmpD/a4gOvvCJQ==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.48.2", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@webassemblyjs/ast": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", - "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", - "dev": true, - "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1" - } - }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", - "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", - "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", - "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", - "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", - "dev": true, - "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", - "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", - "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1" - } - }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", - "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", - "dev": true, - "dependencies": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", - "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", - "dev": true, - "dependencies": { - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", - "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", - "dev": true - }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", - "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/helper-wasm-section": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-opt": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "@webassemblyjs/wast-printer": "1.11.1" - } - }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", - "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" - } - }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", - "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1" - } - }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", - "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" - } - }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", - "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webpack-cli/configtest": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.2.0.tgz", - "integrity": "sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==", - "dev": true, - "peerDependencies": { - "webpack": "4.x.x || 5.x.x", - "webpack-cli": "4.x.x" - } - }, - "node_modules/@webpack-cli/info": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.5.0.tgz", - "integrity": "sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ==", - "dev": true, - "dependencies": { - "envinfo": "^7.7.3" - }, - "peerDependencies": { - "webpack-cli": "4.x.x" - } - }, - "node_modules/@webpack-cli/serve": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.7.0.tgz", - "integrity": "sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==", - "dev": true, - "peerDependencies": { - "webpack-cli": "4.x.x" - }, - "peerDependenciesMeta": { - "webpack-dev-server": { - "optional": true - } - } - }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true - }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true - }, - "node_modules/acorn": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", - "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true - }, - "node_modules/babel-jest": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.3.1.tgz", - "integrity": "sha512-aard+xnMoxgjwV70t0L6wkW/3HQQtV+O0PEimxKgzNqCJnbYmroPojdP2tqKSOAt8QAKV/uSZU8851M7B5+fcA==", - "dev": true, - "dependencies": { - "@jest/transform": "^29.3.1", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.2.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.8.0" - } - }, - "node_modules/babel-jest/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==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/babel-jest/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/babel-jest/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==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/babel-jest/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/babel-jest/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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-jest/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-jest/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==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-loader": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.3.0.tgz", - "integrity": "sha512-H8SvsMF+m9t15HNLMipppzkC+Y2Yq+v3SonZyU70RBL/h1gxPkH08Ot8pEE9Z4Kd+czyWJClmFS8qzIP9OZ04Q==", - "dev": true, - "dependencies": { - "find-cache-dir": "^3.3.1", - "loader-utils": "^2.0.0", - "make-dir": "^3.1.0", - "schema-utils": "^2.6.5" - }, - "engines": { - "node": ">= 8.9" - }, - "peerDependencies": { - "@babel/core": "^7.0.0", - "webpack": ">=2" - } - }, - "node_modules/babel-loader/node_modules/find-cache-dir": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", - "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", - "dev": true, - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/avajs/find-cache-dir?sponsor=1" - } - }, - "node_modules/babel-loader/node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/babel-loader/node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-jest-hoist": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.2.0.tgz", - "integrity": "sha512-TnspP2WNiR3GLfCsUNHqeXw0RoQ2f9U5hQ5L3XFpwuO8htQmSrhh8qsB6vi5Yi8+kuynN1yjDjQsPfkebmB6ZA==", - "dev": true, - "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz", - "integrity": "sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.17.7", - "@babel/helper-define-polyfill-provider": "^0.3.3", - "semver": "^6.1.1" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz", - "integrity": "sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==", - "dev": true, - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.3.3", - "core-js-compat": "^3.25.1" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz", - "integrity": "sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==", - "dev": true, - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.3.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "dev": true, - "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/babel-preset-jest": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.2.0.tgz", - "integrity": "sha512-z9JmMJppMxNv8N7fNRHvhMg9cvIkMxQBXgFkane3yKVEvEOP+kB50lk8DFRvF9PGqbyXxlmebKWhuDORO8RgdA==", - "dev": true, - "dependencies": { - "babel-plugin-jest-hoist": "^29.2.0", - "babel-preset-current-node-syntax": "^1.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "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/big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "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==", - "dev": true, - "optional": true, - "engines": { - "node": ">=8" - } - }, - "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==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.21.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", - "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - } - ], - "dependencies": { - "caniuse-lite": "^1.0.30001400", - "electron-to-chromium": "^1.4.251", - "node-releases": "^2.0.6", - "update-browserslist-db": "^1.0.9" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/bs-logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", - "dev": true, - "dependencies": { - "fast-json-stable-stringify": "2.x" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "dependencies": { - "node-int64": "^0.4.0" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001535", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001535.tgz", - "integrity": "sha512-48jLyUkiWFfhm/afF7cQPqPjaUmSraEhK4j+FCTJpgnGGEZHqyLe3hmWH7lIooZdSzXL0ReMvHz0vKDoTBsrwg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ] - }, - "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "optional": 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" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "dev": true, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/ci-info": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.7.1.tgz", - "integrity": "sha512-4jYS4MOAaCIStSRwiuxc4B8MYhIe676yO1sYGzARnjXkWpmzZMMYxY6zu8WYWDhSuth5zhrQ1rhNSibyyvv4/w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "engines": { - "node": ">=8" - } - }, - "node_modules/cjs-module-lexer": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", - "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", - "dev": true - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true, - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, - "node_modules/collect-v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", - "dev": true - }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/colorette": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", - "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", - "dev": true - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "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, - "engines": { - "node": ">= 6" - } - }, - "node_modules/commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", - "dev": true - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true - }, - "node_modules/core-js": { - "version": "3.27.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.27.1.tgz", - "integrity": "sha512-GutwJLBChfGCpwwhbYoqfv03LAfmiz7e7D/BNxzeMxwQf10GRSzqiOjx7AmtEk+heiD/JWmBuyBPgFtx0Sg1ww==", - "hasInstallScript": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-js-compat": { - "version": "3.27.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.27.1.tgz", - "integrity": "sha512-Dg91JFeCDA17FKnneN7oCMz4BkQ4TcffkgHP4OWwp9yx3pi7ubqMDXXSacfNak1PQqjc95skyt+YBLHQJnkJwA==", - "dev": true, - "dependencies": { - "browserslist": "^4.21.4" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", - "dev": true - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "node_modules/deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/diff-sequences": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.3.1.tgz", - "integrity": "sha512-hlM3QR272NXCi4pq+N4Kok4kOp6EsgOM3ZSpJI7Da3UAs+Ttsi8MRmB6trM/lhyzUxGfOgnpkHtgqm5Q/CTcfQ==", - "dev": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/duplexer": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", - "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", - "dev": true - }, - "node_modules/electron-to-chromium": { - "version": "1.4.284", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz", - "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==", - "dev": true - }, - "node_modules/emittery": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", - "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" - } - }, - "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/emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/enhanced-resolve": { - "version": "5.12.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz", - "integrity": "sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "dependencies": { - "ansi-colors": "^4.1.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/envinfo": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", - "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", - "dev": true, - "bin": { - "envinfo": "dist/cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/es-module-lexer": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", - "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", - "dev": true - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/eslint": { - "version": "7.32.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", - "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.4.3", - "@humanwhocodes/config-array": "^0.5.0", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "enquirer": "^2.3.5", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.1", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.1.2", - "globals": "^13.6.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", - "strip-json-comments": "^3.1.0", - "table": "^6.0.9", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-config-prettier": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-7.2.0.tgz", - "integrity": "sha512-rV4Qu0C3nfJKPOAhFujFxB7RMP+URFyQqqOZW9DMRD7ZDTFyjaIlETU3xzHELt++4ugC0+Jm084HQYkkJe+Ivg==", - "dev": true, - "bin": { - "eslint-config-prettier": "bin/cli.js" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } - }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^2.0.0" - }, - "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint/node_modules/@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.10.4" - } - }, - "node_modules/eslint/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==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/eslint/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/eslint/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==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/eslint/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/eslint/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==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^1.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/eslint/node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint/node_modules/globals": { - "version": "13.19.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", - "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/eslint/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint/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==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/espree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", - "dev": true, - "dependencies": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/espree/node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esquery/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/expect": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.3.1.tgz", - "integrity": "sha512-gGb1yTgU30Q0O/tQq+z30KBWv24ApkMgFUpvKBkyLUBL68Wv8dHdJxTBZFl/iT8K/bqDHvUYRH6IIN3rToopPA==", - "dev": true, - "dependencies": { - "@jest/expect-utils": "^29.3.1", - "jest-get-type": "^29.2.0", - "jest-matcher-utils": "^29.3.1", - "jest-message-util": "^29.3.1", - "jest-util": "^29.3.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "node_modules/fastest-levenshtein": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", - "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", - "dev": true, - "engines": { - "node": ">= 4.9.1" - } - }, - "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "dev": true, - "dependencies": { - "bser": "2.1.1" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "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==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "dev": true, - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "dependencies": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", - "dev": true - }, - "node_modules/form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fs-readdir-recursive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", - "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", - "dev": true - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "node_modules/functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", - "dev": true - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "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==", - "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "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==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true - }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globby/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true - }, - "node_modules/gzip-size": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", - "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", - "dev": true, - "dependencies": { - "duplexer": "^0.1.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/import-fresh/node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", - "dev": true, - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/import-local/node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "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/interpret": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", - "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "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==", - "dev": true, - "optional": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-core-module": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", - "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", - "dev": true, - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "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==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", - "dev": true, - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", - "dev": true, - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report/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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report/node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/istanbul-lib-report/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==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-reports": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", - "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", - "dev": true, - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest/-/jest-29.3.1.tgz", - "integrity": "sha512-6iWfL5DTT0Np6UYs/y5Niu7WIfNv/wRTtN5RSXt2DIEft3dx3zPuw/3WJQBCJfmEzvDiEKwoqMbGD9n49+qLSA==", - "dev": true, - "dependencies": { - "@jest/core": "^29.3.1", - "@jest/types": "^29.3.1", - "import-local": "^3.0.2", - "jest-cli": "^29.3.1" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-changed-files": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.2.0.tgz", - "integrity": "sha512-qPVmLLyBmvF5HJrY7krDisx6Voi8DmlV3GZYX0aFNbaQsZeoz1hfxcCMbqDGuQCxU1dJy9eYc2xscE8QrCCYaA==", - "dev": true, - "dependencies": { - "execa": "^5.0.0", - "p-limit": "^3.1.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-changed-files/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==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-circus": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.3.1.tgz", - "integrity": "sha512-wpr26sEvwb3qQQbdlmei+gzp6yoSSoSL6GsLPxnuayZSMrSd5Ka7IjAvatpIernBvT2+Ic6RLTg+jSebScmasg==", - "dev": true, - "dependencies": { - "@jest/environment": "^29.3.1", - "@jest/expect": "^29.3.1", - "@jest/test-result": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^0.7.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^29.3.1", - "jest-matcher-utils": "^29.3.1", - "jest-message-util": "^29.3.1", - "jest-runtime": "^29.3.1", - "jest-snapshot": "^29.3.1", - "jest-util": "^29.3.1", - "p-limit": "^3.1.0", - "pretty-format": "^29.3.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-circus/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==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-circus/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-circus/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==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-circus/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/jest-circus/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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-circus/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==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-circus/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-circus/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==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-cli": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.3.1.tgz", - "integrity": "sha512-TO/ewvwyvPOiBBuWZ0gm04z3WWP8TIK8acgPzE4IxgsLKQgb377NYGrQLc3Wl/7ndWzIH2CDNNsUjGxwLL43VQ==", - "dev": true, - "dependencies": { - "@jest/core": "^29.3.1", - "@jest/test-result": "^29.3.1", - "@jest/types": "^29.3.1", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "import-local": "^3.0.2", - "jest-config": "^29.3.1", - "jest-util": "^29.3.1", - "jest-validate": "^29.3.1", - "prompts": "^2.0.1", - "yargs": "^17.3.1" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-cli/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==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-cli/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-cli/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==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-cli/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/jest-cli/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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-cli/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==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-config": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.3.1.tgz", - "integrity": "sha512-y0tFHdj2WnTEhxmGUK1T7fgLen7YK4RtfvpLFBXfQkh2eMJAQq24Vx9472lvn5wg0MAO6B+iPfJfzdR9hJYalg==", - "dev": true, - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.3.1", - "@jest/types": "^29.3.1", - "babel-jest": "^29.3.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^29.3.1", - "jest-environment-node": "^29.3.1", - "jest-get-type": "^29.2.0", - "jest-regex-util": "^29.2.0", - "jest-resolve": "^29.3.1", - "jest-runner": "^29.3.1", - "jest-util": "^29.3.1", - "jest-validate": "^29.3.1", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^29.3.1", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@types/node": "*", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "ts-node": { - "optional": true - } - } - }, - "node_modules/jest-config/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==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-config/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-config/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==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-config/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/jest-config/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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-config/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-config/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==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-diff": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.3.1.tgz", - "integrity": "sha512-vU8vyiO7568tmin2lA3r2DP8oRvzhvRcD4DjpXc6uGveQodyk7CKLhQlCSiwgx3g0pFaE88/KLZ0yaTWMc4Uiw==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^29.3.1", - "jest-get-type": "^29.2.0", - "pretty-format": "^29.3.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-diff/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==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-diff/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-diff/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==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-diff/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/jest-diff/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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-diff/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==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-docblock": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.2.0.tgz", - "integrity": "sha512-bkxUsxTgWQGbXV5IENmfiIuqZhJcyvF7tU4zJ/7ioTutdz4ToB5Yx6JOFBpgI+TphRY4lhOyCWGNH/QFQh5T6A==", - "dev": true, - "dependencies": { - "detect-newline": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-each": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.3.1.tgz", - "integrity": "sha512-qrZH7PmFB9rEzCSl00BWjZYuS1BSOH8lLuC0azQE9lQrAx3PWGKHTDudQiOSwIy5dGAJh7KA0ScYlCP7JxvFYA==", - "dev": true, - "dependencies": { - "@jest/types": "^29.3.1", - "chalk": "^4.0.0", - "jest-get-type": "^29.2.0", - "jest-util": "^29.3.1", - "pretty-format": "^29.3.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-each/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==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-each/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-each/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==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-each/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/jest-each/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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-each/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==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-environment-node": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.3.1.tgz", - "integrity": "sha512-xm2THL18Xf5sIHoU7OThBPtuH6Lerd+Y1NLYiZJlkE3hbE+7N7r8uvHIl/FkZ5ymKXJe/11SQuf3fv4v6rUMag==", - "dev": true, - "dependencies": { - "@jest/environment": "^29.3.1", - "@jest/fake-timers": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/node": "*", - "jest-mock": "^29.3.1", - "jest-util": "^29.3.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-get-type": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.2.0.tgz", - "integrity": "sha512-uXNJlg8hKFEnDgFsrCjznB+sTxdkuqiCL6zMgA75qEbAJjJYTs9XPrvDctrEig2GDow22T/LvHgO57iJhXB/UA==", - "dev": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-haste-map": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.3.1.tgz", - "integrity": "sha512-/FFtvoG1xjbbPXQLFef+WSU4yrc0fc0Dds6aRPBojUid7qlPqZvxdUBA03HW0fnVHXVCnCdkuoghYItKNzc/0A==", - "dev": true, - "dependencies": { - "@jest/types": "^29.3.1", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.2.0", - "jest-util": "^29.3.1", - "jest-worker": "^29.3.1", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" - } - }, - "node_modules/jest-leak-detector": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.3.1.tgz", - "integrity": "sha512-3DA/VVXj4zFOPagGkuqHnSQf1GZBmmlagpguxEERO6Pla2g84Q1MaVIB3YMxgUaFIaYag8ZnTyQgiZ35YEqAQA==", - "dev": true, - "dependencies": { - "jest-get-type": "^29.2.0", - "pretty-format": "^29.3.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-matcher-utils": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.3.1.tgz", - "integrity": "sha512-fkRMZUAScup3txIKfMe3AIZZmPEjWEdsPJFK3AIy5qRohWqQFg1qrmKfYXR9qEkNc7OdAu2N4KPHibEmy4HPeQ==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^29.3.1", - "jest-get-type": "^29.2.0", - "pretty-format": "^29.3.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-matcher-utils/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==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-matcher-utils/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-matcher-utils/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==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-matcher-utils/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/jest-matcher-utils/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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-matcher-utils/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==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-message-util": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.3.1.tgz", - "integrity": "sha512-lMJTbgNcDm5z+6KDxWtqOFWlGQxD6XaYwBqHR8kmpkP+WWWG90I35kdtQHY67Ay5CSuydkTBbJG+tH9JShFCyA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.3.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.3.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-message-util/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==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-message-util/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-message-util/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==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-message-util/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/jest-message-util/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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-message-util/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-message-util/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==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-mock": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.3.1.tgz", - "integrity": "sha512-H8/qFDtDVMFvFP4X8NuOT3XRDzOUTz+FeACjufHzsOIBAxivLqkB1PoLCaJx9iPPQ8dZThHPp/G3WRWyMgA3JA==", - "dev": true, - "dependencies": { - "@jest/types": "^29.3.1", - "@types/node": "*", - "jest-util": "^29.3.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true, - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" - }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } - } - }, - "node_modules/jest-regex-util": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.2.0.tgz", - "integrity": "sha512-6yXn0kg2JXzH30cr2NlThF+70iuO/3irbaB4mh5WyqNIvLLP+B6sFdluO1/1RJmslyh/f9osnefECflHvTbwVA==", - "dev": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-resolve": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.3.1.tgz", - "integrity": "sha512-amXJgH/Ng712w3Uz5gqzFBBjxV8WFLSmNjoreBGMqxgCz5cH7swmBZzgBaCIOsvb0NbpJ0vgaSFdJqMdT+rADw==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.3.1", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.3.1", - "jest-validate": "^29.3.1", - "resolve": "^1.20.0", - "resolve.exports": "^1.1.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-resolve-dependencies": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.3.1.tgz", - "integrity": "sha512-Vk0cYq0byRw2WluNmNWGqPeRnZ3p3hHmjJMp2dyyZeYIfiBskwq4rpiuGFR6QGAdbj58WC7HN4hQHjf2mpvrLA==", - "dev": true, - "dependencies": { - "jest-regex-util": "^29.2.0", - "jest-snapshot": "^29.3.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-resolve/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==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-resolve/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-resolve/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==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-resolve/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/jest-resolve/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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-resolve/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-resolve/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==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runner": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.3.1.tgz", - "integrity": "sha512-oFvcwRNrKMtE6u9+AQPMATxFcTySyKfLhvso7Sdk/rNpbhg4g2GAGCopiInk1OP4q6gz3n6MajW4+fnHWlU3bA==", - "dev": true, - "dependencies": { - "@jest/console": "^29.3.1", - "@jest/environment": "^29.3.1", - "@jest/test-result": "^29.3.1", - "@jest/transform": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "graceful-fs": "^4.2.9", - "jest-docblock": "^29.2.0", - "jest-environment-node": "^29.3.1", - "jest-haste-map": "^29.3.1", - "jest-leak-detector": "^29.3.1", - "jest-message-util": "^29.3.1", - "jest-resolve": "^29.3.1", - "jest-runtime": "^29.3.1", - "jest-util": "^29.3.1", - "jest-watcher": "^29.3.1", - "jest-worker": "^29.3.1", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runner/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==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-runner/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-runner/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==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-runner/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/jest-runner/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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runner/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==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-runner/node_modules/source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/jest-runner/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==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runtime": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.3.1.tgz", - "integrity": "sha512-jLzkIxIqXwBEOZx7wx9OO9sxoZmgT2NhmQKzHQm1xwR1kNW/dn0OjxR424VwHHf1SPN6Qwlb5pp1oGCeFTQ62A==", - "dev": true, - "dependencies": { - "@jest/environment": "^29.3.1", - "@jest/fake-timers": "^29.3.1", - "@jest/globals": "^29.3.1", - "@jest/source-map": "^29.2.0", - "@jest/test-result": "^29.3.1", - "@jest/transform": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/node": "*", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.3.1", - "jest-message-util": "^29.3.1", - "jest-mock": "^29.3.1", - "jest-regex-util": "^29.2.0", - "jest-resolve": "^29.3.1", - "jest-snapshot": "^29.3.1", - "jest-util": "^29.3.1", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runtime/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==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-runtime/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-runtime/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==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-runtime/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/jest-runtime/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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runtime/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runtime/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==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-snapshot": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.3.1.tgz", - "integrity": "sha512-+3JOc+s28upYLI2OJM4PWRGK9AgpsMs/ekNryUV0yMBClT9B1DF2u2qay8YxcQd338PPYSFNb0lsar1B49sLDA==", - "dev": true, - "dependencies": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-jsx": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.3.1", - "@jest/transform": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/babel__traverse": "^7.0.6", - "@types/prettier": "^2.1.5", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^29.3.1", - "graceful-fs": "^4.2.9", - "jest-diff": "^29.3.1", - "jest-get-type": "^29.2.0", - "jest-haste-map": "^29.3.1", - "jest-matcher-utils": "^29.3.1", - "jest-message-util": "^29.3.1", - "jest-util": "^29.3.1", - "natural-compare": "^1.4.0", - "pretty-format": "^29.3.1", - "semver": "^7.3.5" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-snapshot/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==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-snapshot/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-snapshot/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==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-snapshot/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/jest-snapshot/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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-snapshot/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-snapshot/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==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-snapshot/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/jest-util": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.3.1.tgz", - "integrity": "sha512-7YOVZaiX7RJLv76ZfHt4nbNEzzTRiMW/IiOG7ZOKmTXmoGBxUDefgMAxQubu6WPVqP5zSzAdZG0FfLcC7HOIFQ==", - "dev": true, - "dependencies": { - "@jest/types": "^29.3.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-util/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==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-util/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-util/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==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-util/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/jest-util/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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-util/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==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-validate": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.3.1.tgz", - "integrity": "sha512-N9Lr3oYR2Mpzuelp1F8negJR3YE+L1ebk1rYA5qYo9TTY3f9OWdptLoNSPP9itOCBIRBqjt/S5XHlzYglLN67g==", - "dev": true, - "dependencies": { - "@jest/types": "^29.3.1", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^29.2.0", - "leven": "^3.1.0", - "pretty-format": "^29.3.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-validate/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==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-validate/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-validate/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==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-validate/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/jest-validate/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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-validate/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==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-watcher": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.3.1.tgz", - "integrity": "sha512-RspXG2BQFDsZSRKGCT/NiNa8RkQ1iKAjrO0//soTMWx/QUt+OcxMqMSBxz23PYGqUuWm2+m2mNNsmj0eIoOaFg==", - "dev": true, - "dependencies": { - "@jest/test-result": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "jest-util": "^29.3.1", - "string-length": "^4.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-watcher/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==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-watcher/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-watcher/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==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-watcher/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/jest-watcher/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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-watcher/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==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-worker": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.3.1.tgz", - "integrity": "sha512-lY4AnnmsEWeiXirAIA0c9SDPbuCBq8IYuDVL8PMm0MZ2PEs2yPvRA/J64QBXuZp7CYKrDM/rmNrc9/i3KJQncw==", - "dev": true, - "dependencies": { - "@types/node": "*", - "jest-util": "^29.3.1", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-worker/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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-worker/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==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "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 - }, - "node_modules/loader-runner": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", - "dev": true, - "engines": { - "node": ">=6.11.5" - } - }, - "node_modules/loader-utils": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", - "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - }, - "engines": { - "node": ">=8.9.0" - } - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "node_modules/lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "dev": true - }, - "node_modules/lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", - "dev": true - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, - "dependencies": { - "pify": "^4.0.1", - "semver": "^5.6.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "node_modules/makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, - "dependencies": { - "tmpl": "1.0.5" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/mrmime": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz", - "integrity": "sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "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/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "node_modules/natural-compare-lite": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", - "dev": true - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true - }, - "node_modules/node-fetch": { - "version": "2.6.8", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.8.tgz", - "integrity": "sha512-RZ6dBYuj8dRSfxpUSu+NsdF1dpPpluJxwOp+6IoDp/sH2QNDSvurYsAa+F1WxY2RjA1iP93xhcsUoYbF2XBqVg==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/node-fetch/node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "node_modules/node-fetch/node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "node_modules/node-fetch/node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true - }, - "node_modules/node-releases": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.8.tgz", - "integrity": "sha512-dFSmB8fFHEH/s81Xi+Y/15DQY6VHW81nXRj86EMSL3lmuTmK1e+aT4wrFCkTbm+gSwkw4KpX+rT/pMM2c1mF+A==", - "dev": true - }, - "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==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/opener": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", - "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", - "dev": true, - "bin": { - "opener": "bin/opener-bin.js" - } - }, - "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "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": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/pirates": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", - "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "dev": true, - "dependencies": { - "find-up": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-dir/node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-dir/node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-dir/node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-dir/node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.1.tgz", - "integrity": "sha512-lqGoSJBQNJidqCHE80vqZJHWHRFoNYsSpP9AjFhlhi9ODCJA541svILes/+/1GM3VaL/abZi7cpFzOpdR9UPKg==", - "dev": true, - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/pretty-format": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.3.1.tgz", - "integrity": "sha512-FyLnmb1cYJV8biEIiRyzRFvs2lry7PPIvOqKVe1GCUEYg4YGmlx1qG9EJNMxArYm7piII4qb8UV1Pncq5dxmcg==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "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/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "optional": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/rechoir": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz", - "integrity": "sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==", - "dev": true, - "dependencies": { - "resolve": "^1.9.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "dev": true - }, - "node_modules/regenerate-unicode-properties": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", - "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", - "dev": true, - "dependencies": { - "regenerate": "^1.4.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", - "dev": true - }, - "node_modules/regenerator-transform": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.1.tgz", - "integrity": "sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.8.4" - } - }, - "node_modules/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/regexpu-core": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.2.2.tgz", - "integrity": "sha512-T0+1Zp2wjF/juXMrMxHxidqGYn8U4R+zleSJhX9tQ1PUsS8a9UtYfbsF9LdiVgNX3kiX8RNaKM42nfSgvFJjmw==", - "dev": true, - "dependencies": { - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.1.0", - "regjsgen": "^0.7.1", - "regjsparser": "^0.9.1", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regjsgen": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.7.1.tgz", - "integrity": "sha512-RAt+8H2ZEzHeYWxZ3H2z6tF18zyyOnlcdaafLrm21Bguj7uZy6ULibiAFdXEtKQY4Sy7wDTwDiOazasMLc4KPA==", - "dev": true - }, - "node_modules/regjsparser": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", - "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", - "dev": true, - "dependencies": { - "jsesc": "~0.5.0" - }, - "bin": { - "regjsparser": "bin/parser" - } - }, - "node_modules/regjsparser/node_modules/jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", - "dev": true, - "dependencies": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "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, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve.exports": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.1.tgz", - "integrity": "sha512-/NtpHNDN7jWhAaQ9BvBUYZ6YTXsRBgfqWFWP7BZBaoMJO/I3G5OFzvTuWNlZC3aPjins1F+TNrLKsGbH4rfsRQ==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rfdc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", - "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==" - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "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==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/schema-utils": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", - "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.5", - "ajv": "^6.12.4", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 8.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "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, - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "node_modules/sirv": { - "version": "1.0.19", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-1.0.19.tgz", - "integrity": "sha512-JuLThK3TnZG1TAKDwNIqNq6QA2afLOCcm+iE8D1Kj3GA40pSPsxQjjJl0J8X3tsR7T+CP1GavpzLwYkgVLWrZQ==", - "dev": true, - "dependencies": { - "@polka/url": "^1.0.0-next.20", - "mrmime": "^1.0.0", - "totalist": "^1.0.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true - }, - "node_modules/slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/slice-ansi/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==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/slice-ansi/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==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/slice-ansi/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/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true - }, - "node_modules/stack-utils": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/stack-utils/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "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==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/table": { - "version": "6.8.1", - "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz", - "integrity": "sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==", - "dev": true, - "dependencies": { - "ajv": "^8.0.1", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/table/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/table/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, - "node_modules/tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/terser": { - "version": "5.16.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.16.1.tgz", - "integrity": "sha512-xvQfyfA1ayT0qdK47zskQgRZeWLoOQ8JQ6mIgRGVNwZKdQMU+5FkCBjmv4QjcrTzyZquRw2FVtlJSRUmMKQslw==", - "dev": true, - "dependencies": { - "@jridgewell/source-map": "^0.3.2", - "acorn": "^8.5.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser-webpack-plugin": { - "version": "5.3.6", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.6.tgz", - "integrity": "sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.14", - "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.0", - "terser": "^5.14.1" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "uglify-js": { - "optional": true - } - } - }, - "node_modules/terser-webpack-plugin/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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/terser-webpack-plugin/node_modules/jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dev": true, - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/terser-webpack-plugin/node_modules/schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/terser-webpack-plugin/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==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true - }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "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==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/totalist": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/totalist/-/totalist-1.1.0.tgz", - "integrity": "sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/ts-jest": { - "version": "29.0.5", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.0.5.tgz", - "integrity": "sha512-PL3UciSgIpQ7f6XjVOmbi96vmDHUqAyqDr8YxzopDqX3kfgYtX1cuNeBjP+L9sFXi6nzsGGA6R3fP3DDDJyrxA==", - "dev": true, - "dependencies": { - "bs-logger": "0.x", - "fast-json-stable-stringify": "2.x", - "jest-util": "^29.0.0", - "json5": "^2.2.3", - "lodash.memoize": "4.x", - "make-error": "1.x", - "semver": "7.x", - "yargs-parser": "^21.0.1" - }, - "bin": { - "ts-jest": "cli.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": ">=7.0.0-beta.0 <8", - "@jest/types": "^29.0.0", - "babel-jest": "^29.0.0", - "jest": "^29.0.0", - "typescript": ">=4.3" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "@jest/types": { - "optional": true - }, - "babel-jest": { - "optional": true - }, - "esbuild": { - "optional": true - } - } - }, - "node_modules/ts-jest/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ts-jest/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ts-jest/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/ts-loader": { - "version": "9.4.2", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.2.tgz", - "integrity": "sha512-OmlC4WVmFv5I0PpaxYb+qGeGOdm5giHU7HwDDUjw59emP2UYMHy9fFSDcYgSNoH8sXcj4hGCSEhlDZ9ULeDraA==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "enhanced-resolve": "^5.0.0", - "micromatch": "^4.0.0", - "semver": "^7.3.4" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "typescript": "*", - "webpack": "^5.0.0" - } - }, - "node_modules/ts-loader/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==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/ts-loader/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/ts-loader/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==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/ts-loader/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/ts-loader/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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ts-loader/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ts-loader/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ts-loader/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==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ts-loader/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "dependencies": { - "tslib": "^1.8.1" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - } - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typescript": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", - "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, - "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "dev": true, - "dependencies": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", - "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", - "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", - "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - } - ], - "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - }, - "bin": { - "browserslist-lint": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "node_modules/v8-to-istanbul": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz", - "integrity": "sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0" - }, - "engines": { - "node": ">=10.12.0" - } - }, - "node_modules/walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dev": true, - "dependencies": { - "makeerror": "1.0.12" - } - }, - "node_modules/watchpack": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", - "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", - "dev": true, - "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/webpack": { - "version": "5.76.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.0.tgz", - "integrity": "sha512-l5sOdYBDunyf72HW8dF23rFtWq/7Zgvt/9ftMof71E/yUb1YLOBmTgA2K4vQthB3kotMrSj609txVE0dnr2fjA==", - "dev": true, - "dependencies": { - "@types/eslint-scope": "^3.7.3", - "@types/estree": "^0.0.51", - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/wasm-edit": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "acorn": "^8.7.1", - "acorn-import-assertions": "^1.7.6", - "browserslist": "^4.14.5", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.10.0", - "es-module-lexer": "^0.9.0", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^3.1.0", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.1.3", - "watchpack": "^2.4.0", - "webpack-sources": "^3.2.3" - }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/webpack-bundle-analyzer": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.7.0.tgz", - "integrity": "sha512-j9b8ynpJS4K+zfO5GGwsAcQX4ZHpWV+yRiHDiL+bE0XHJ8NiPYLTNVQdlFYWxtpg9lfAQNlwJg16J9AJtFSXRg==", - "dev": true, - "dependencies": { - "acorn": "^8.0.4", - "acorn-walk": "^8.0.0", - "chalk": "^4.1.0", - "commander": "^7.2.0", - "gzip-size": "^6.0.0", - "lodash": "^4.17.20", - "opener": "^1.5.2", - "sirv": "^1.0.7", - "ws": "^7.3.1" - }, - "bin": { - "webpack-bundle-analyzer": "lib/bin/analyzer.js" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/webpack-bundle-analyzer/node_modules/acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/webpack-bundle-analyzer/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==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/webpack-bundle-analyzer/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/webpack-bundle-analyzer/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==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/webpack-bundle-analyzer/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/webpack-bundle-analyzer/node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "dev": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/webpack-bundle-analyzer/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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/webpack-bundle-analyzer/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==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/webpack-cli": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz", - "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==", - "dev": true, - "dependencies": { - "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^1.2.0", - "@webpack-cli/info": "^1.5.0", - "@webpack-cli/serve": "^1.7.0", - "colorette": "^2.0.14", - "commander": "^7.0.0", - "cross-spawn": "^7.0.3", - "fastest-levenshtein": "^1.0.12", - "import-local": "^3.0.2", - "interpret": "^2.2.0", - "rechoir": "^0.7.0", - "webpack-merge": "^5.7.3" - }, - "bin": { - "webpack-cli": "bin/cli.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "4.x.x || 5.x.x" - }, - "peerDependenciesMeta": { - "@webpack-cli/generators": { - "optional": true - }, - "@webpack-cli/migrate": { - "optional": true - }, - "webpack-bundle-analyzer": { - "optional": true - }, - "webpack-dev-server": { - "optional": true - } - } - }, - "node_modules/webpack-cli/node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "dev": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/webpack-merge": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", - "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==", - "dev": true, - "dependencies": { - "clone-deep": "^4.0.1", - "wildcard": "^2.0.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", - "dev": true, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/webpack/node_modules/acorn-import-assertions": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", - "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", - "dev": true, - "peerDependencies": { - "acorn": "^8" - } - }, - "node_modules/webpack/node_modules/schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wildcard": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", - "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", - "dev": true - }, - "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "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==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi/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==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/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==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/wrap-ansi/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/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "node_modules/write-file-atomic": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", - "dev": true, - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - }, - "node_modules/yargs": { - "version": "17.6.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz", - "integrity": "sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==", - "dev": true, - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "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==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - }, - "dependencies": { - "@ampproject/remapping": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", - "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", - "dev": true, - "requires": { - "@jridgewell/gen-mapping": "^0.1.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "@babel/cli": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.20.7.tgz", - "integrity": "sha512-WylgcELHB66WwQqItxNILsMlaTd8/SO6SgTTjMp4uCI7P4QyH1r3nqgFmO3BfM4AtfniHgFMH3EpYFj/zynBkQ==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "^0.3.8", - "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents.3", - "chokidar": "^3.4.0", - "commander": "^4.0.1", - "convert-source-map": "^1.1.0", - "fs-readdir-recursive": "^1.1.0", - "glob": "^7.2.0", - "make-dir": "^2.1.0", - "slash": "^2.0.0" - } - }, - "@babel/code-frame": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.4.tgz", - "integrity": "sha512-r1IONyb6Ia+jYR2vvIDhdWdlTGhqbBoFqLTQidzZ4kepUFH15ejXvFHxCVbtl7BOXIudsIubf4E81xeA3h3IXA==", - "dev": true, - "requires": { - "@babel/highlight": "^7.23.4", - "chalk": "^2.4.2" - } - }, - "@babel/compat-data": { - "version": "7.20.10", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.10.tgz", - "integrity": "sha512-sEnuDPpOJR/fcafHMjpcpGN5M2jbUGUHwmuWKM/YdPzeEDJg8bgmbcWQFUfE32MQjti1koACvoPVsDe8Uq+idg==", - "dev": true - }, - "@babel/core": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.7.tgz", - "integrity": "sha512-t1ZjCluspe5DW24bn2Rr1CDb2v9rn/hROtg9a2tmd0+QYf4bsloYfLQzjG4qHPNMhWtKdGC33R5AxGR2Af2cBw==", - "dev": true, - "requires": { - "@ampproject/remapping": "^2.1.0", - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.20.7", - "@babel/helper-compilation-targets": "^7.20.7", - "@babel/helper-module-transforms": "^7.20.7", - "@babel/helpers": "^7.20.7", - "@babel/parser": "^7.20.7", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.20.7", - "@babel/types": "^7.20.7", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.1", - "semver": "^6.3.0" - } - }, - "@babel/eslint-parser": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.19.1.tgz", - "integrity": "sha512-AqNf2QWt1rtu2/1rLswy6CDP7H9Oh3mMhk177Y67Rg8d7RD9WfOLLv8CGn6tisFvS2htm86yIe1yLF6I1UDaGQ==", - "dev": true, - "requires": { - "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", - "eslint-visitor-keys": "^2.1.0", - "semver": "^6.3.0" - } - }, - "@babel/generator": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.4.tgz", - "integrity": "sha512-esuS49Cga3HcThFNebGhlgsrVLkvhqvYDTzgjfFFlHJcIfLe5jFmRRfCQ1KuBfc4Jrtn3ndLgKWAKjBE+IraYQ==", - "dev": true, - "requires": { - "@babel/types": "^7.23.4", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", - "jsesc": "^2.5.1" - }, - "dependencies": { - "@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dev": true, - "requires": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - } - } - } - }, - "@babel/helper-annotate-as-pure": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", - "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", - "dev": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz", - "integrity": "sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==", - "dev": true, - "requires": { - "@babel/helper-explode-assignable-expression": "^7.18.6", - "@babel/types": "^7.18.9" - } - }, - "@babel/helper-compilation-targets": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz", - "integrity": "sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.20.5", - "@babel/helper-validator-option": "^7.18.6", - "browserslist": "^4.21.3", - "lru-cache": "^5.1.1", - "semver": "^6.3.0" - } - }, - "@babel/helper-create-class-features-plugin": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.20.7.tgz", - "integrity": "sha512-LtoWbDXOaidEf50hmdDqn9g8VEzsorMexoWMQdQODbvmqYmaF23pBP5VNPAGIFHsFQCIeKokDiz3CH5Y2jlY6w==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-member-expression-to-functions": "^7.20.7", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/helper-replace-supers": "^7.20.7", - "@babel/helper-split-export-declaration": "^7.18.6" - } - }, - "@babel/helper-create-regexp-features-plugin": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.20.5.tgz", - "integrity": "sha512-m68B1lkg3XDGX5yCvGO0kPx3v9WIYLnzjKfPcQiwntEQa5ZeRkPmo2X/ISJc8qxWGfwUr+kvZAeEzAwLec2r2w==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "regexpu-core": "^5.2.1" - } - }, - "@babel/helper-define-polyfill-provider": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz", - "integrity": "sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==", - "dev": true, - "requires": { - "@babel/helper-compilation-targets": "^7.17.7", - "@babel/helper-plugin-utils": "^7.16.7", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2", - "semver": "^6.1.2" - } - }, - "@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", - "dev": true - }, - "@babel/helper-explode-assignable-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz", - "integrity": "sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==", - "dev": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", - "dev": true, - "requires": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", - "dev": true, - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.20.7.tgz", - "integrity": "sha512-9J0CxJLq315fEdi4s7xK5TQaNYjZw+nDVpVqr1axNGKzdrdwYBD5b4uKv3n75aABG0rCCTK8Im8Ww7eYfMrZgw==", - "dev": true, - "requires": { - "@babel/types": "^7.20.7" - } - }, - "@babel/helper-module-imports": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", - "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", - "dev": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-module-transforms": { - "version": "7.20.11", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.11.tgz", - "integrity": "sha512-uRy78kN4psmji1s2QtbtcCSaj/LILFDp0f/ymhpQH5QY3nljUZCaNWz9X1dEj/8MBdBEFECs7yRhKn8i7NjZgg==", - "dev": true, - "requires": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-simple-access": "^7.20.2", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/helper-validator-identifier": "^7.19.1", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.20.10", - "@babel/types": "^7.20.7" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", - "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", - "dev": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz", - "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==", - "dev": true - }, - "@babel/helper-remap-async-to-generator": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz", - "integrity": "sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-wrap-function": "^7.18.9", - "@babel/types": "^7.18.9" - } - }, - "@babel/helper-replace-supers": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.20.7.tgz", - "integrity": "sha512-vujDMtB6LVfNW13jhlCrp48QNslK6JXi7lQG736HVbHz/mbf4Dc7tIRh1Xf5C0rF7BP8iiSxGMCmY6Ci1ven3A==", - "dev": true, - "requires": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-member-expression-to-functions": "^7.20.7", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.20.7", - "@babel/types": "^7.20.7" - } - }, - "@babel/helper-simple-access": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz", - "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==", - "dev": true, - "requires": { - "@babel/types": "^7.20.2" - } - }, - "@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz", - "integrity": "sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==", - "dev": true, - "requires": { - "@babel/types": "^7.20.0" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", - "dev": true, - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-string-parser": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", - "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", - "dev": true - }, - "@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", - "dev": true - }, - "@babel/helper-validator-option": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", - "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", - "dev": true - }, - "@babel/helper-wrap-function": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.20.5.tgz", - "integrity": "sha512-bYMxIWK5mh+TgXGVqAtnu5Yn1un+v8DDZtqyzKRLUzrh70Eal2O3aZ7aPYiMADO4uKlkzOiRiZ6GX5q3qxvW9Q==", - "dev": true, - "requires": { - "@babel/helper-function-name": "^7.19.0", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.20.5", - "@babel/types": "^7.20.5" - } - }, - "@babel/helpers": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.7.tgz", - "integrity": "sha512-PBPjs5BppzsGaxHQCDKnZ6Gd9s6xl8bBCluz3vEInLGRJmnZan4F6BYCeqtyXqkk4W5IlPmjK4JlOuZkpJ3xZA==", - "dev": true, - "requires": { - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.20.7", - "@babel/types": "^7.20.7" - } - }, - "@babel/highlight": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", - "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.22.20", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.4.tgz", - "integrity": "sha512-vf3Xna6UEprW+7t6EtOmFpHNAuxw3xqPZghy+brsnusscJRW5BMUzzHZc5ICjULee81WeUV2jjakG09MDglJXQ==", - "dev": true - }, - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz", - "integrity": "sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.20.7.tgz", - "integrity": "sha512-sbr9+wNE5aXMBBFBICk01tt7sBf2Oc9ikRFEcem/ZORup9IMUdNhW7/wVLEbbtlWOsEubJet46mHAL2C8+2jKQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", - "@babel/plugin-proposal-optional-chaining": "^7.20.7" - } - }, - "@babel/plugin-proposal-async-generator-functions": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz", - "integrity": "sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==", - "dev": true, - "requires": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-remap-async-to-generator": "^7.18.9", - "@babel/plugin-syntax-async-generators": "^7.8.4" - } - }, - "@babel/plugin-proposal-class-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", - "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", - "dev": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-proposal-class-static-block": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.20.7.tgz", - "integrity": "sha512-AveGOoi9DAjUYYuUAG//Ig69GlazLnoyzMw68VCDux+c1tsnnH/OkYcpz/5xzMkEFC6UxjR5Gw1c+iY2wOGVeQ==", - "dev": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.20.7", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-class-static-block": "^7.14.5" - } - }, - "@babel/plugin-proposal-dynamic-import": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz", - "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" - } - }, - "@babel/plugin-proposal-export-default-from": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.18.10.tgz", - "integrity": "sha512-5H2N3R2aQFxkV4PIBUR/i7PUSwgTZjouJKzI8eKswfIjT0PhvzkPn0t0wIS5zn6maQuvtT0t1oHtMUz61LOuow==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-export-default-from": "^7.18.6" - } - }, - "@babel/plugin-proposal-export-namespace-from": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", - "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" - } - }, - "@babel/plugin-proposal-json-strings": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz", - "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-json-strings": "^7.8.3" - } - }, - "@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.20.7.tgz", - "integrity": "sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" - } - }, - "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", - "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" - } - }, - "@babel/plugin-proposal-numeric-separator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", - "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" - } - }, - "@babel/plugin-proposal-object-rest-spread": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz", - "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.20.5", - "@babel/helper-compilation-targets": "^7.20.7", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.20.7" - } - }, - "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", - "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" - } - }, - "@babel/plugin-proposal-optional-chaining": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.20.7.tgz", - "integrity": "sha512-T+A7b1kfjtRM51ssoOfS1+wbyCVqorfyZhT99TvxxLMirPShD8CzKMRepMlCBGM5RpHMbn8s+5MMHnPstJH6mQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" - } - }, - "@babel/plugin-proposal-private-methods": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", - "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", - "dev": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-proposal-private-property-in-object": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.20.5.tgz", - "integrity": "sha512-Vq7b9dUA12ByzB4EjQTPo25sFhY+08pQDBSZRtUAkj7lb7jahaHR5igera16QZ+3my1nYR4dKsNdYj5IjPHilQ==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-create-class-features-plugin": "^7.20.5", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" - } - }, - "@babel/plugin-proposal-throw-expressions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-throw-expressions/-/plugin-proposal-throw-expressions-7.18.6.tgz", - "integrity": "sha512-WHOrJyhGoGrdtW480L79cF7Iq/gZDZ/z6OqK7mVyFR5I37dTpog/wNgb6hmaM3HYZtULEJl++7VaMWkNZsOcHg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-throw-expressions": "^7.18.6" - } - }, - "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", - "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-export-default-from": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.18.6.tgz", - "integrity": "sha512-Kr//z3ujSVNx6E9z9ih5xXXMqK07VVTuqPmqGe6Mss/zW5XPeLZeSDZoP9ab/hT4wPKqAgjl2PnhPrcpk8Seew==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-syntax-import-assertions": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz", - "integrity": "sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.19.0" - } - }, - "@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-jsx": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", - "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-throw-expressions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-throw-expressions/-/plugin-syntax-throw-expressions-7.18.6.tgz", - "integrity": "sha512-rp1CqEZXGv1z1YZ3qYffBH3rhnOxrTwQG8fh2yqulTurwv9zu3Gthfd+niZBLSOi1rY6146TgF+JmVeDXaX4TQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-typescript": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.20.0.tgz", - "integrity": "sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.19.0" - } - }, - "@babel/plugin-transform-arrow-functions": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.20.7.tgz", - "integrity": "sha512-3poA5E7dzDomxj9WXWwuD6A5F3kc7VXwIJO+E+J8qtDtS+pXPAhrgEyh+9GBwBgPq1Z+bB+/JD60lp5jsN7JPQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.20.2" - } - }, - "@babel/plugin-transform-async-to-generator": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.20.7.tgz", - "integrity": "sha512-Uo5gwHPT9vgnSXQxqGtpdufUiWp96gk7yiP4Mp5bm1QMkEmLXBO7PAGYbKoJ6DhAwiNkcHFBol/x5zZZkL/t0Q==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-remap-async-to-generator": "^7.18.9" - } - }, - "@babel/plugin-transform-block-scoped-functions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz", - "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-block-scoping": { - "version": "7.20.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.11.tgz", - "integrity": "sha512-tA4N427a7fjf1P0/2I4ScsHGc5jcHPbb30xMbaTke2gxDuWpUfXDuX1FEymJwKk4tuGUvGcejAR6HdZVqmmPyw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.20.2" - } - }, - "@babel/plugin-transform-classes": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.20.7.tgz", - "integrity": "sha512-LWYbsiXTPKl+oBlXUGlwNlJZetXD5Am+CyBdqhPsDVjM9Jc8jwBJFrKhHf900Kfk2eZG1y9MAG3UNajol7A4VQ==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-compilation-targets": "^7.20.7", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-replace-supers": "^7.20.7", - "@babel/helper-split-export-declaration": "^7.18.6", - "globals": "^11.1.0" - } - }, - "@babel/plugin-transform-computed-properties": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.20.7.tgz", - "integrity": "sha512-Lz7MvBK6DTjElHAmfu6bfANzKcxpyNPeYBGEafyA6E5HtRpjpZwU+u7Qrgz/2OR0z+5TvKYbPdphfSaAcZBrYQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/template": "^7.20.7" - } - }, - "@babel/plugin-transform-destructuring": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.7.tgz", - "integrity": "sha512-Xwg403sRrZb81IVB79ZPqNQME23yhugYVqgTxAhT99h485F4f+GMELFhhOsscDUB7HCswepKeCKLn/GZvUKoBA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.20.2" - } - }, - "@babel/plugin-transform-dotall-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz", - "integrity": "sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-duplicate-keys": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz", - "integrity": "sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-exponentiation-operator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz", - "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==", - "dev": true, - "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-for-of": { - "version": "7.18.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz", - "integrity": "sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-function-name": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz", - "integrity": "sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==", - "dev": true, - "requires": { - "@babel/helper-compilation-targets": "^7.18.9", - "@babel/helper-function-name": "^7.18.9", - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-literals": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz", - "integrity": "sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-member-expression-literals": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz", - "integrity": "sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-modules-amd": { - "version": "7.20.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.20.11.tgz", - "integrity": "sha512-NuzCt5IIYOW0O30UvqktzHYR2ud5bOWbY0yaxWZ6G+aFzOMJvrs5YHNikrbdaT15+KNO31nPOy5Fim3ku6Zb5g==", - "dev": true, - "requires": { - "@babel/helper-module-transforms": "^7.20.11", - "@babel/helper-plugin-utils": "^7.20.2" - } - }, - "@babel/plugin-transform-modules-commonjs": { - "version": "7.20.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.20.11.tgz", - "integrity": "sha512-S8e1f7WQ7cimJQ51JkAaDrEtohVEitXjgCGAS2N8S31Y42E+kWwfSz83LYz57QdBm7q9diARVqanIaH2oVgQnw==", - "dev": true, - "requires": { - "@babel/helper-module-transforms": "^7.20.11", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-simple-access": "^7.20.2" - } - }, - "@babel/plugin-transform-modules-systemjs": { - "version": "7.20.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.20.11.tgz", - "integrity": "sha512-vVu5g9BPQKSFEmvt2TA4Da5N+QVS66EX21d8uoOihC+OCpUoGvzVsXeqFdtAEfVa5BILAeFt+U7yVmLbQnAJmw==", - "dev": true, - "requires": { - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-module-transforms": "^7.20.11", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-validator-identifier": "^7.19.1" - } - }, - "@babel/plugin-transform-modules-umd": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz", - "integrity": "sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==", - "dev": true, - "requires": { - "@babel/helper-module-transforms": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.20.5.tgz", - "integrity": "sha512-mOW4tTzi5iTLnw+78iEq3gr8Aoq4WNRGpmSlrogqaiCBoR1HFhpU4JkpQFOHfeYx3ReVIFWOQJS4aZBRvuZ6mA==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.20.5", - "@babel/helper-plugin-utils": "^7.20.2" - } - }, - "@babel/plugin-transform-new-target": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz", - "integrity": "sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-object-super": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz", - "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-replace-supers": "^7.18.6" - } - }, - "@babel/plugin-transform-parameters": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.20.7.tgz", - "integrity": "sha512-WiWBIkeHKVOSYPO0pWkxGPfKeWrCJyD3NJ53+Lrp/QMSZbsVPovrVl2aWZ19D/LTVnaDv5Ap7GJ/B2CTOZdrfA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.20.2" - } - }, - "@babel/plugin-transform-property-literals": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz", - "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-regenerator": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.20.5.tgz", - "integrity": "sha512-kW/oO7HPBtntbsahzQ0qSE3tFvkFwnbozz3NWFhLGqH75vLEg+sCGngLlhVkePlCs3Jv0dBBHDzCHxNiFAQKCQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.20.2", - "regenerator-transform": "^0.15.1" - } - }, - "@babel/plugin-transform-reserved-words": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz", - "integrity": "sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-runtime": { - "version": "7.19.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.19.6.tgz", - "integrity": "sha512-PRH37lz4JU156lYFW1p8OxE5i7d6Sl/zV58ooyr+q1J1lnQPyg5tIiXlIwNVhJaY4W3TmOtdc8jqdXQcB1v5Yw==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.19.0", - "babel-plugin-polyfill-corejs2": "^0.3.3", - "babel-plugin-polyfill-corejs3": "^0.6.0", - "babel-plugin-polyfill-regenerator": "^0.4.1", - "semver": "^6.3.0" - } - }, - "@babel/plugin-transform-shorthand-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz", - "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-spread": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.20.7.tgz", - "integrity": "sha512-ewBbHQ+1U/VnH1fxltbJqDeWBU1oNLG8Dj11uIv3xVf7nrQu0bPGe5Rf716r7K5Qz+SqtAOVswoVunoiBtGhxw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0" - } - }, - "@babel/plugin-transform-sticky-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz", - "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-template-literals": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz", - "integrity": "sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-typeof-symbol": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz", - "integrity": "sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-typescript": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.20.7.tgz", - "integrity": "sha512-m3wVKEvf6SoszD8pu4NZz3PvfKRCMgk6D6d0Qi9hNnlM5M6CFS92EgF4EiHVLKbU0r/r7ty1hg7NPZwE7WRbYw==", - "dev": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.20.7", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-typescript": "^7.20.0" - } - }, - "@babel/plugin-transform-unicode-escapes": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz", - "integrity": "sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-unicode-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz", - "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/preset-env": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.20.2.tgz", - "integrity": "sha512-1G0efQEWR1EHkKvKHqbG+IN/QdgwfByUpM5V5QroDzGV2t3S/WXNQd693cHiHTlCFMpr9B6FkPFXDA2lQcKoDg==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.20.1", - "@babel/helper-compilation-targets": "^7.20.0", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-validator-option": "^7.18.6", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.18.9", - "@babel/plugin-proposal-async-generator-functions": "^7.20.1", - "@babel/plugin-proposal-class-properties": "^7.18.6", - "@babel/plugin-proposal-class-static-block": "^7.18.6", - "@babel/plugin-proposal-dynamic-import": "^7.18.6", - "@babel/plugin-proposal-export-namespace-from": "^7.18.9", - "@babel/plugin-proposal-json-strings": "^7.18.6", - "@babel/plugin-proposal-logical-assignment-operators": "^7.18.9", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", - "@babel/plugin-proposal-numeric-separator": "^7.18.6", - "@babel/plugin-proposal-object-rest-spread": "^7.20.2", - "@babel/plugin-proposal-optional-catch-binding": "^7.18.6", - "@babel/plugin-proposal-optional-chaining": "^7.18.9", - "@babel/plugin-proposal-private-methods": "^7.18.6", - "@babel/plugin-proposal-private-property-in-object": "^7.18.6", - "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.20.0", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-transform-arrow-functions": "^7.18.6", - "@babel/plugin-transform-async-to-generator": "^7.18.6", - "@babel/plugin-transform-block-scoped-functions": "^7.18.6", - "@babel/plugin-transform-block-scoping": "^7.20.2", - "@babel/plugin-transform-classes": "^7.20.2", - "@babel/plugin-transform-computed-properties": "^7.18.9", - "@babel/plugin-transform-destructuring": "^7.20.2", - "@babel/plugin-transform-dotall-regex": "^7.18.6", - "@babel/plugin-transform-duplicate-keys": "^7.18.9", - "@babel/plugin-transform-exponentiation-operator": "^7.18.6", - "@babel/plugin-transform-for-of": "^7.18.8", - "@babel/plugin-transform-function-name": "^7.18.9", - "@babel/plugin-transform-literals": "^7.18.9", - "@babel/plugin-transform-member-expression-literals": "^7.18.6", - "@babel/plugin-transform-modules-amd": "^7.19.6", - "@babel/plugin-transform-modules-commonjs": "^7.19.6", - "@babel/plugin-transform-modules-systemjs": "^7.19.6", - "@babel/plugin-transform-modules-umd": "^7.18.6", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.19.1", - "@babel/plugin-transform-new-target": "^7.18.6", - "@babel/plugin-transform-object-super": "^7.18.6", - "@babel/plugin-transform-parameters": "^7.20.1", - "@babel/plugin-transform-property-literals": "^7.18.6", - "@babel/plugin-transform-regenerator": "^7.18.6", - "@babel/plugin-transform-reserved-words": "^7.18.6", - "@babel/plugin-transform-shorthand-properties": "^7.18.6", - "@babel/plugin-transform-spread": "^7.19.0", - "@babel/plugin-transform-sticky-regex": "^7.18.6", - "@babel/plugin-transform-template-literals": "^7.18.9", - "@babel/plugin-transform-typeof-symbol": "^7.18.9", - "@babel/plugin-transform-unicode-escapes": "^7.18.10", - "@babel/plugin-transform-unicode-regex": "^7.18.6", - "@babel/preset-modules": "^0.1.5", - "@babel/types": "^7.20.2", - "babel-plugin-polyfill-corejs2": "^0.3.3", - "babel-plugin-polyfill-corejs3": "^0.6.0", - "babel-plugin-polyfill-regenerator": "^0.4.1", - "core-js-compat": "^3.25.1", - "semver": "^6.3.0" - } - }, - "@babel/preset-modules": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", - "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", - "@babel/plugin-transform-dotall-regex": "^7.4.4", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - } - }, - "@babel/preset-typescript": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.18.6.tgz", - "integrity": "sha512-s9ik86kXBAnD760aybBucdpnLsAt0jK1xqJn2juOn9lkOvSHV60os5hxoVJsPzMQxvnUJFAlkont2DvvaYEBtQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-validator-option": "^7.18.6", - "@babel/plugin-transform-typescript": "^7.18.6" - } - }, - "@babel/register": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.18.9.tgz", - "integrity": "sha512-ZlbnXDcNYHMR25ITwwNKT88JiaukkdVj/nG7r3wnuXkOTHc60Uy05PwMCPre0hSkY68E6zK3xz+vUJSP2jWmcw==", - "dev": true, - "requires": { - "clone-deep": "^4.0.1", - "find-cache-dir": "^2.0.0", - "make-dir": "^2.1.0", - "pirates": "^4.0.5", - "source-map-support": "^0.5.16" - } - }, - "@babel/runtime": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz", - "integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==", - "dev": true, - "requires": { - "regenerator-runtime": "^0.13.11" - } - }, - "@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" - } - }, - "@babel/traverse": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.4.tgz", - "integrity": "sha512-IYM8wSUwunWTB6tFC2dkKZhxbIjHoWemdK+3f8/wq8aKhbUscxD5MX72ubd90fxvFknaLPeGw5ycU84V1obHJg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.23.4", - "@babel/generator": "^7.23.4", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.4", - "@babel/types": "^7.23.4", - "debug": "^4.1.0", - "globals": "^11.1.0" - } - }, - "@babel/types": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.4.tgz", - "integrity": "sha512-7uIFwVYpoplT5jp/kVv6EF93VaJ8H+Yn5IczYiaAi98ajzjfoZfslet/e0sLh+wVBjb2qqIut1b0S26VSafsSQ==", - "dev": true, - "requires": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", - "to-fast-properties": "^2.0.0" - } - }, - "@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true - }, - "@discoveryjs/json-ext": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", - "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", - "dev": true - }, - "@eslint/eslintrc": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", - "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", - "dev": true, - "requires": { - "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", - "globals": "^13.9.0", - "ignore": "^4.0.6", - "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "globals": { - "version": "13.19.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", - "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - } - } - }, - "@humanwhocodes/config-array": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", - "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", - "dev": true, - "requires": { - "@humanwhocodes/object-schema": "^1.2.0", - "debug": "^4.1.1", - "minimatch": "^3.0.4" - } - }, - "@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true - }, - "@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "requires": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - } - }, - "@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true - }, - "@jest/console": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.3.1.tgz", - "integrity": "sha512-IRE6GD47KwcqA09RIWrabKdHPiKDGgtAL31xDxbi/RjQMsr+lY+ppxmHwY0dUEV3qvvxZzoe5Hl0RXZJOjQNUg==", - "dev": true, - "requires": { - "@jest/types": "^29.3.1", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.3.1", - "jest-util": "^29.3.1", - "slash": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "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==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/core": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.3.1.tgz", - "integrity": "sha512-0ohVjjRex985w5MmO5L3u5GR1O30DexhBSpuwx2P+9ftyqHdJXnk7IUWiP80oHMvt7ubHCJHxV0a0vlKVuZirw==", - "dev": true, - "requires": { - "@jest/console": "^29.3.1", - "@jest/reporters": "^29.3.1", - "@jest/test-result": "^29.3.1", - "@jest/transform": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.2.0", - "jest-config": "^29.3.1", - "jest-haste-map": "^29.3.1", - "jest-message-util": "^29.3.1", - "jest-regex-util": "^29.2.0", - "jest-resolve": "^29.3.1", - "jest-resolve-dependencies": "^29.3.1", - "jest-runner": "^29.3.1", - "jest-runtime": "^29.3.1", - "jest-snapshot": "^29.3.1", - "jest-util": "^29.3.1", - "jest-validate": "^29.3.1", - "jest-watcher": "^29.3.1", - "micromatch": "^4.0.4", - "pretty-format": "^29.3.1", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "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==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/environment": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.3.1.tgz", - "integrity": "sha512-pMmvfOPmoa1c1QpfFW0nXYtNLpofqo4BrCIk6f2kW4JFeNlHV2t3vd+3iDLf31e2ot2Mec0uqZfmI+U0K2CFag==", - "dev": true, - "requires": { - "@jest/fake-timers": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/node": "*", - "jest-mock": "^29.3.1" - } - }, - "@jest/expect": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.3.1.tgz", - "integrity": "sha512-QivM7GlSHSsIAWzgfyP8dgeExPRZ9BIe2LsdPyEhCGkZkoyA+kGsoIzbKAfZCvvRzfZioKwPtCZIt5SaoxYCvg==", - "dev": true, - "requires": { - "expect": "^29.3.1", - "jest-snapshot": "^29.3.1" - } - }, - "@jest/expect-utils": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.3.1.tgz", - "integrity": "sha512-wlrznINZI5sMjwvUoLVk617ll/UYfGIZNxmbU+Pa7wmkL4vYzhV9R2pwVqUh4NWWuLQWkI8+8mOkxs//prKQ3g==", - "dev": true, - "requires": { - "jest-get-type": "^29.2.0" - } - }, - "@jest/fake-timers": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.3.1.tgz", - "integrity": "sha512-iHTL/XpnDlFki9Tq0Q1GGuVeQ8BHZGIYsvCO5eN/O/oJaRzofG9Xndd9HuSDBI/0ZS79pg0iwn07OMTQ7ngF2A==", - "dev": true, - "requires": { - "@jest/types": "^29.3.1", - "@sinonjs/fake-timers": "^9.1.2", - "@types/node": "*", - "jest-message-util": "^29.3.1", - "jest-mock": "^29.3.1", - "jest-util": "^29.3.1" - } - }, - "@jest/globals": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.3.1.tgz", - "integrity": "sha512-cTicd134vOcwO59OPaB6AmdHQMCtWOe+/DitpTZVxWgMJ+YvXL1HNAmPyiGbSHmF/mXVBkvlm8YYtQhyHPnV6Q==", - "dev": true, - "requires": { - "@jest/environment": "^29.3.1", - "@jest/expect": "^29.3.1", - "@jest/types": "^29.3.1", - "jest-mock": "^29.3.1" - } - }, - "@jest/reporters": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.3.1.tgz", - "integrity": "sha512-GhBu3YFuDrcAYW/UESz1JphEAbvUjaY2vShRZRoRY1mxpCMB3yGSJ4j9n0GxVlEOdCf7qjvUfBCrTUUqhVfbRA==", - "dev": true, - "requires": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.3.1", - "@jest/test-result": "^29.3.1", - "@jest/transform": "^29.3.1", - "@jest/types": "^29.3.1", - "@jridgewell/trace-mapping": "^0.3.15", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^5.1.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.3.1", - "jest-util": "^29.3.1", - "jest-worker": "^29.3.1", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "v8-to-istanbul": "^9.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "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==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/schemas": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", - "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.24.1" - } - }, - "@jest/source-map": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.2.0.tgz", - "integrity": "sha512-1NX9/7zzI0nqa6+kgpSdKPK+WU1p+SJk3TloWZf5MzPbxri9UEeXX5bWZAPCzbQcyuAzubcdUHA7hcNznmRqWQ==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "^0.3.15", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" - } - }, - "@jest/test-result": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.3.1.tgz", - "integrity": "sha512-qeLa6qc0ddB0kuOZyZIhfN5q0e2htngokyTWsGriedsDhItisW7SDYZ7ceOe57Ii03sL988/03wAcBh3TChMGw==", - "dev": true, - "requires": { - "@jest/console": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - } - }, - "@jest/test-sequencer": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.3.1.tgz", - "integrity": "sha512-IqYvLbieTv20ArgKoAMyhLHNrVHJfzO6ARZAbQRlY4UGWfdDnLlZEF0BvKOMd77uIiIjSZRwq3Jb3Fa3I8+2UA==", - "dev": true, - "requires": { - "@jest/test-result": "^29.3.1", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.3.1", - "slash": "^3.0.0" - }, - "dependencies": { - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - } - } - }, - "@jest/transform": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.3.1.tgz", - "integrity": "sha512-8wmCFBTVGYqFNLWfcOWoVuMuKYPUBTnTMDkdvFtAYELwDOl9RGwOsvQWGPFxDJ8AWY9xM/8xCXdqmPK3+Q5Lug==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.3.1", - "@jridgewell/trace-mapping": "^0.3.15", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.3.1", - "jest-regex-util": "^29.2.0", - "jest-util": "^29.3.1", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "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==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/types": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.3.1.tgz", - "integrity": "sha512-d0S0jmmTpjnhCmNpApgX3jrUZgZ22ivKJRvL2lli5hpCRoNnp1f85r2/wpKfXuYu8E7Jjh1hGfhPyup1NM5AmA==", - "dev": true, - "requires": { - "@jest/schemas": "^29.0.0", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "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==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jridgewell/gen-mapping": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", - "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", - "dev": true, - "requires": { - "@jridgewell/set-array": "^1.0.0", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "dev": true - }, - "@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true - }, - "@jridgewell/source-map": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", - "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", - "dev": true, - "requires": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "dependencies": { - "@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dev": true, - "requires": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - } - } - } - }, - "@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true - }, - "@jridgewell/trace-mapping": { - "version": "0.3.17", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", - "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", - "dev": true, - "requires": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" - } - }, - "@nicolo-ribaudo/chokidar-2": { - "version": "2.1.8-no-fsevents.3", - "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz", - "integrity": "sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==", - "dev": true, - "optional": true - }, - "@nicolo-ribaudo/eslint-scope-5-internals": { - "version": "5.1.1-v1", - "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", - "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", - "dev": true, - "requires": { - "eslint-scope": "5.1.1" - } - }, - "@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - } - }, - "@polka/url": { - "version": "1.0.0-next.21", - "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz", - "integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==", - "dev": true - }, - "@sinclair/typebox": { - "version": "0.24.51", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", - "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==", - "dev": true - }, - "@sinonjs/commons": { - "version": "1.8.6", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", - "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - }, - "@sinonjs/fake-timers": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", - "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.7.0" - } - }, - "@types/babel__core": { - "version": "7.1.20", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.20.tgz", - "integrity": "sha512-PVb6Bg2QuscZ30FvOU7z4guG6c926D9YRvOxEaelzndpMsvP+YM74Q/dAFASpg2l6+XLalxSGxcq/lrgYWZtyQ==", - "dev": true, - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "@types/babel__generator": { - "version": "7.6.4", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", - "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@types/babel__template": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", - "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", - "dev": true, - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@types/babel__traverse": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.3.tgz", - "integrity": "sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==", - "dev": true, - "requires": { - "@babel/types": "^7.3.0" - } - }, - "@types/eslint": { - "version": "8.4.10", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.10.tgz", - "integrity": "sha512-Sl/HOqN8NKPmhWo2VBEPm0nvHnu2LL3v9vKo8MEq0EtbJ4eVzGPl41VNPvn5E1i5poMk4/XD8UriLHpJvEP/Nw==", - "dev": true, - "requires": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "@types/eslint-scope": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", - "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", - "dev": true, - "requires": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, - "@types/estree": { - "version": "0.0.51", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", - "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", - "dev": true - }, - "@types/graceful-fs": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz", - "integrity": "sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/istanbul-lib-coverage": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", - "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", - "dev": true - }, - "@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "*" - } - }, - "@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "@types/jest": { - "version": "29.2.5", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.2.5.tgz", - "integrity": "sha512-H2cSxkKgVmqNHXP7TC2L/WUorrZu8ZigyRywfVzv6EyBlxj39n4C00hjXYQWsbwqgElaj/CiAeSRmk5GoaKTgw==", - "dev": true, - "requires": { - "expect": "^29.0.0", - "pretty-format": "^29.0.0" - } - }, - "@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", - "dev": true - }, - "@types/node": { - "version": "18.11.18", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", - "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==", - "dev": true - }, - "@types/node-fetch": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.2.tgz", - "integrity": "sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A==", - "dev": true, - "requires": { - "@types/node": "*", - "form-data": "^3.0.0" - } - }, - "@types/prettier": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz", - "integrity": "sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==", - "dev": true - }, - "@types/semver": { - "version": "7.3.13", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", - "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", - "dev": true - }, - "@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", - "dev": true - }, - "@types/yargs": { - "version": "17.0.19", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.19.tgz", - "integrity": "sha512-cAx3qamwaYX9R0fzOIZAlFpo4A+1uBVCxqpKz9D26uTF4srRXaGTTsikQmaotCtNdbhzyUH7ft6p9ktz9s6UNQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "@types/yargs-parser": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", - "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", - "dev": true - }, - "@typescript-eslint/eslint-plugin": { - "version": "5.48.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.48.2.tgz", - "integrity": "sha512-sR0Gja9Ky1teIq4qJOl0nC+Tk64/uYdX+mi+5iB//MH8gwyx8e3SOyhEzeLZEFEEfCaLf8KJq+Bd/6je1t+CAg==", - "dev": true, - "requires": { - "@typescript-eslint/scope-manager": "5.48.2", - "@typescript-eslint/type-utils": "5.48.2", - "@typescript-eslint/utils": "5.48.2", - "debug": "^4.3.4", - "ignore": "^5.2.0", - "natural-compare-lite": "^1.4.0", - "regexpp": "^3.2.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "dependencies": { - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } - }, - "@typescript-eslint/parser": { - "version": "5.48.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.48.2.tgz", - "integrity": "sha512-38zMsKsG2sIuM5Oi/olurGwYJXzmtdsHhn5mI/pQogP+BjYVkK5iRazCQ8RGS0V+YLk282uWElN70zAAUmaYHw==", - "dev": true, - "requires": { - "@typescript-eslint/scope-manager": "5.48.2", - "@typescript-eslint/types": "5.48.2", - "@typescript-eslint/typescript-estree": "5.48.2", - "debug": "^4.3.4" - } - }, - "@typescript-eslint/scope-manager": { - "version": "5.48.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.48.2.tgz", - "integrity": "sha512-zEUFfonQid5KRDKoI3O+uP1GnrFd4tIHlvs+sTJXiWuypUWMuDaottkJuR612wQfOkjYbsaskSIURV9xo4f+Fw==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.48.2", - "@typescript-eslint/visitor-keys": "5.48.2" - } - }, - "@typescript-eslint/type-utils": { - "version": "5.48.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.48.2.tgz", - "integrity": "sha512-QVWx7J5sPMRiOMJp5dYshPxABRoZV1xbRirqSk8yuIIsu0nvMTZesKErEA3Oix1k+uvsk8Cs8TGJ6kQ0ndAcew==", - "dev": true, - "requires": { - "@typescript-eslint/typescript-estree": "5.48.2", - "@typescript-eslint/utils": "5.48.2", - "debug": "^4.3.4", - "tsutils": "^3.21.0" - } - }, - "@typescript-eslint/types": { - "version": "5.48.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.48.2.tgz", - "integrity": "sha512-hE7dA77xxu7ByBc6KCzikgfRyBCTst6dZQpwaTy25iMYOnbNljDT4hjhrGEJJ0QoMjrfqrx+j1l1B9/LtKeuqA==", - "dev": true - }, - "@typescript-eslint/typescript-estree": { - "version": "5.48.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.48.2.tgz", - "integrity": "sha512-bibvD3z6ilnoVxUBFEgkO0k0aFvUc4Cttt0dAreEr+nrAHhWzkO83PEVVuieK3DqcgL6VAK5dkzK8XUVja5Zcg==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.48.2", - "@typescript-eslint/visitor-keys": "5.48.2", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "dependencies": { - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } - }, - "@typescript-eslint/utils": { - "version": "5.48.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.48.2.tgz", - "integrity": "sha512-2h18c0d7jgkw6tdKTlNaM7wyopbLRBiit8oAxoP89YnuBOzCZ8g8aBCaCqq7h208qUTroL7Whgzam7UY3HVLow==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.48.2", - "@typescript-eslint/types": "5.48.2", - "@typescript-eslint/typescript-estree": "5.48.2", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0", - "semver": "^7.3.7" - }, - "dependencies": { - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } - }, - "@typescript-eslint/visitor-keys": { - "version": "5.48.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.48.2.tgz", - "integrity": "sha512-z9njZLSkwmjFWUelGEwEbdf4NwKvfHxvGC0OcGN1Hp/XNDIcJ7D5DpPNPv6x6/mFvc1tQHsaWmpD/a4gOvvCJQ==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.48.2", - "eslint-visitor-keys": "^3.3.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", - "dev": true - } - } - }, - "@webassemblyjs/ast": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", - "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", - "dev": true, - "requires": { - "@webassemblyjs/helper-numbers": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1" - } - }, - "@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", - "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", - "dev": true - }, - "@webassemblyjs/helper-api-error": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", - "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", - "dev": true - }, - "@webassemblyjs/helper-buffer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", - "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", - "dev": true - }, - "@webassemblyjs/helper-numbers": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", - "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", - "dev": true, - "requires": { - "@webassemblyjs/floating-point-hex-parser": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@xtuc/long": "4.2.2" - } - }, - "@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", - "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", - "dev": true - }, - "@webassemblyjs/helper-wasm-section": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", - "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1" - } - }, - "@webassemblyjs/ieee754": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", - "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", - "dev": true, - "requires": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "@webassemblyjs/leb128": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", - "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", - "dev": true, - "requires": { - "@xtuc/long": "4.2.2" - } - }, - "@webassemblyjs/utf8": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", - "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", - "dev": true - }, - "@webassemblyjs/wasm-edit": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", - "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/helper-wasm-section": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-opt": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "@webassemblyjs/wast-printer": "1.11.1" - } - }, - "@webassemblyjs/wasm-gen": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", - "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" - } - }, - "@webassemblyjs/wasm-opt": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", - "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1" - } - }, - "@webassemblyjs/wasm-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", - "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" - } - }, - "@webassemblyjs/wast-printer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", - "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@xtuc/long": "4.2.2" - } - }, - "@webpack-cli/configtest": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.2.0.tgz", - "integrity": "sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==", - "dev": true, - "requires": {} - }, - "@webpack-cli/info": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.5.0.tgz", - "integrity": "sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ==", - "dev": true, - "requires": { - "envinfo": "^7.7.3" - } - }, - "@webpack-cli/serve": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.7.0.tgz", - "integrity": "sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==", - "dev": true, - "requires": {} - }, - "@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true - }, - "@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true - }, - "acorn": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", - "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", - "dev": true - }, - "acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "requires": {} - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "requires": {} - }, - "ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true - }, - "ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "requires": { - "type-fest": "^0.21.3" - } - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true - }, - "astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true - }, - "babel-jest": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.3.1.tgz", - "integrity": "sha512-aard+xnMoxgjwV70t0L6wkW/3HQQtV+O0PEimxKgzNqCJnbYmroPojdP2tqKSOAt8QAKV/uSZU8851M7B5+fcA==", - "dev": true, - "requires": { - "@jest/transform": "^29.3.1", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.2.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "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==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "babel-loader": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.3.0.tgz", - "integrity": "sha512-H8SvsMF+m9t15HNLMipppzkC+Y2Yq+v3SonZyU70RBL/h1gxPkH08Ot8pEE9Z4Kd+czyWJClmFS8qzIP9OZ04Q==", - "dev": true, - "requires": { - "find-cache-dir": "^3.3.1", - "loader-utils": "^2.0.0", - "make-dir": "^3.1.0", - "schema-utils": "^2.6.5" - }, - "dependencies": { - "find-cache-dir": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", - "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", - "dev": true, - "requires": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - } - }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "requires": { - "semver": "^6.0.0" - } - }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "requires": { - "find-up": "^4.0.0" - } - } - } - }, - "babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - } - }, - "babel-plugin-jest-hoist": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.2.0.tgz", - "integrity": "sha512-TnspP2WNiR3GLfCsUNHqeXw0RoQ2f9U5hQ5L3XFpwuO8htQmSrhh8qsB6vi5Yi8+kuynN1yjDjQsPfkebmB6ZA==", - "dev": true, - "requires": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" - } - }, - "babel-plugin-polyfill-corejs2": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz", - "integrity": "sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.17.7", - "@babel/helper-define-polyfill-provider": "^0.3.3", - "semver": "^6.1.1" - } - }, - "babel-plugin-polyfill-corejs3": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz", - "integrity": "sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==", - "dev": true, - "requires": { - "@babel/helper-define-polyfill-provider": "^0.3.3", - "core-js-compat": "^3.25.1" - } - }, - "babel-plugin-polyfill-regenerator": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz", - "integrity": "sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==", - "dev": true, - "requires": { - "@babel/helper-define-polyfill-provider": "^0.3.3" - } - }, - "babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "dev": true, - "requires": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" - } - }, - "babel-preset-jest": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.2.0.tgz", - "integrity": "sha512-z9JmMJppMxNv8N7fNRHvhMg9cvIkMxQBXgFkane3yKVEvEOP+kB50lk8DFRvF9PGqbyXxlmebKWhuDORO8RgdA==", - "dev": true, - "requires": { - "babel-plugin-jest-hoist": "^29.2.0", - "babel-preset-current-node-syntax": "^1.0.0" - } - }, - "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 - }, - "big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true - }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, - "optional": true - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "browserslist": { - "version": "4.21.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", - "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001400", - "electron-to-chromium": "^1.4.251", - "node-releases": "^2.0.6", - "update-browserslist-db": "^1.0.9" - } - }, - "bs-logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", - "dev": true, - "requires": { - "fast-json-stable-stringify": "2.x" - } - }, - "bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "requires": { - "node-int64": "^0.4.0" - } - }, - "buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "caniuse-lite": { - "version": "1.0.30001535", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001535.tgz", - "integrity": "sha512-48jLyUkiWFfhm/afF7cQPqPjaUmSraEhK4j+FCTJpgnGGEZHqyLe3hmWH7lIooZdSzXL0ReMvHz0vKDoTBsrwg==", - "dev": true - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true - }, - "chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "optional": true, - "requires": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "fsevents": "~2.3.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" - } - }, - "chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "dev": true - }, - "ci-info": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.7.1.tgz", - "integrity": "sha512-4jYS4MOAaCIStSRwiuxc4B8MYhIe676yO1sYGzARnjXkWpmzZMMYxY6zu8WYWDhSuth5zhrQ1rhNSibyyvv4/w==", - "dev": true - }, - "cjs-module-lexer": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", - "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", - "dev": true - }, - "cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - } - }, - "clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - } - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true - }, - "collect-v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", - "dev": true - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "colorette": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", - "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", - "dev": true - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true - }, - "commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true - }, - "core-js": { - "version": "3.27.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.27.1.tgz", - "integrity": "sha512-GutwJLBChfGCpwwhbYoqfv03LAfmiz7e7D/BNxzeMxwQf10GRSzqiOjx7AmtEk+heiD/JWmBuyBPgFtx0Sg1ww==" - }, - "core-js-compat": { - "version": "3.27.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.27.1.tgz", - "integrity": "sha512-Dg91JFeCDA17FKnneN7oCMz4BkQ4TcffkgHP4OWwp9yx3pi7ubqMDXXSacfNak1PQqjc95skyt+YBLHQJnkJwA==", - "dev": true, - "requires": { - "browserslist": "^4.21.4" - } - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", - "dev": true - }, - "deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", - "dev": true - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true - }, - "detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true - }, - "diff-sequences": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.3.1.tgz", - "integrity": "sha512-hlM3QR272NXCi4pq+N4Kok4kOp6EsgOM3ZSpJI7Da3UAs+Ttsi8MRmB6trM/lhyzUxGfOgnpkHtgqm5Q/CTcfQ==", - "dev": true - }, - "dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "requires": { - "path-type": "^4.0.0" - } - }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "duplexer": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", - "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", - "dev": true - }, - "electron-to-chromium": { - "version": "1.4.284", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz", - "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==", - "dev": true - }, - "emittery": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", - "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", - "dev": true - }, - "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 - }, - "emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "dev": true - }, - "enhanced-resolve": { - "version": "5.12.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz", - "integrity": "sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - } - }, - "enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "requires": { - "ansi-colors": "^4.1.1" - } - }, - "envinfo": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", - "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", - "dev": true - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "es-module-lexer": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", - "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", - "dev": true - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true - }, - "eslint": { - "version": "7.32.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", - "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", - "dev": true, - "requires": { - "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.4.3", - "@humanwhocodes/config-array": "^0.5.0", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "enquirer": "^2.3.5", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.1", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.1.2", - "globals": "^13.6.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", - "strip-json-comments": "^3.1.0", - "table": "^6.0.9", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.10.4" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "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==", - "dev": true - }, - "eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^1.1.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } - } - }, - "globals": { - "version": "13.19.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", - "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "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==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } - }, - "eslint-config-prettier": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-7.2.0.tgz", - "integrity": "sha512-rV4Qu0C3nfJKPOAhFujFxB7RMP+URFyQqqOZW9DMRD7ZDTFyjaIlETU3xzHELt++4ugC0+Jm084HQYkkJe+Ivg==", - "dev": true, - "requires": {} - }, - "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - } - }, - "eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^2.0.0" - } - }, - "eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true - }, - "espree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", - "dev": true, - "requires": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" - }, - "dependencies": { - "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true - }, - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - }, - "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - } - } - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "requires": { - "estraverse": "^5.2.0" - }, - "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - } - } - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true - }, - "execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - } - }, - "exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", - "dev": true - }, - "expect": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.3.1.tgz", - "integrity": "sha512-gGb1yTgU30Q0O/tQq+z30KBWv24ApkMgFUpvKBkyLUBL68Wv8dHdJxTBZFl/iT8K/bqDHvUYRH6IIN3rToopPA==", - "dev": true, - "requires": { - "@jest/expect-utils": "^29.3.1", - "jest-get-type": "^29.2.0", - "jest-matcher-utils": "^29.3.1", - "jest-message-util": "^29.3.1", - "jest-util": "^29.3.1" - } - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - } - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "fastest-levenshtein": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", - "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", - "dev": true - }, - "fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, - "requires": { - "reusify": "^1.0.4" - } - }, - "fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "dev": true, - "requires": { - "bser": "2.1.1" - } - }, - "file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "requires": { - "flat-cache": "^3.0.4" - } - }, - "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==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "dev": true, - "requires": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "requires": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - } - }, - "flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", - "dev": true - }, - "form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, - "fs-readdir-recursive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", - "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", - "dev": true - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", - "dev": true - }, - "gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true - }, - "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==", - "dev": true - }, - "get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true - }, - "get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true - }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true - }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true - }, - "globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "requires": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "dependencies": { - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - } - } - }, - "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true - }, - "gzip-size": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", - "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", - "dev": true, - "requires": { - "duplexer": "^0.1.2" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true - }, - "html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true - }, - "ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "dev": true - }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - } - } - }, - "import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", - "dev": true, - "requires": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "dependencies": { - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "requires": { - "find-up": "^4.0.0" - } - } - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "interpret": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", - "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", - "dev": true - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true - }, - "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==", - "dev": true, - "optional": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-core-module": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", - "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true - }, - "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==", - "dev": true - }, - "is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true - }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true - }, - "istanbul-lib-coverage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", - "dev": true - }, - "istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", - "dev": true, - "requires": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - } - }, - "istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", - "dev": true, - "requires": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "requires": { - "semver": "^6.0.0" - } - }, - "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==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, - "requires": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - } - }, - "istanbul-reports": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", - "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", - "dev": true, - "requires": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - } - }, - "jest": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest/-/jest-29.3.1.tgz", - "integrity": "sha512-6iWfL5DTT0Np6UYs/y5Niu7WIfNv/wRTtN5RSXt2DIEft3dx3zPuw/3WJQBCJfmEzvDiEKwoqMbGD9n49+qLSA==", - "dev": true, - "requires": { - "@jest/core": "^29.3.1", - "@jest/types": "^29.3.1", - "import-local": "^3.0.2", - "jest-cli": "^29.3.1" - } - }, - "jest-changed-files": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.2.0.tgz", - "integrity": "sha512-qPVmLLyBmvF5HJrY7krDisx6Voi8DmlV3GZYX0aFNbaQsZeoz1hfxcCMbqDGuQCxU1dJy9eYc2xscE8QrCCYaA==", - "dev": true, - "requires": { - "execa": "^5.0.0", - "p-limit": "^3.1.0" - }, - "dependencies": { - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "requires": { - "yocto-queue": "^0.1.0" - } - } - } - }, - "jest-circus": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.3.1.tgz", - "integrity": "sha512-wpr26sEvwb3qQQbdlmei+gzp6yoSSoSL6GsLPxnuayZSMrSd5Ka7IjAvatpIernBvT2+Ic6RLTg+jSebScmasg==", - "dev": true, - "requires": { - "@jest/environment": "^29.3.1", - "@jest/expect": "^29.3.1", - "@jest/test-result": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^0.7.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^29.3.1", - "jest-matcher-utils": "^29.3.1", - "jest-message-util": "^29.3.1", - "jest-runtime": "^29.3.1", - "jest-snapshot": "^29.3.1", - "jest-util": "^29.3.1", - "p-limit": "^3.1.0", - "pretty-format": "^29.3.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "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==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-cli": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.3.1.tgz", - "integrity": "sha512-TO/ewvwyvPOiBBuWZ0gm04z3WWP8TIK8acgPzE4IxgsLKQgb377NYGrQLc3Wl/7ndWzIH2CDNNsUjGxwLL43VQ==", - "dev": true, - "requires": { - "@jest/core": "^29.3.1", - "@jest/test-result": "^29.3.1", - "@jest/types": "^29.3.1", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "import-local": "^3.0.2", - "jest-config": "^29.3.1", - "jest-util": "^29.3.1", - "jest-validate": "^29.3.1", - "prompts": "^2.0.1", - "yargs": "^17.3.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "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==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-config": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.3.1.tgz", - "integrity": "sha512-y0tFHdj2WnTEhxmGUK1T7fgLen7YK4RtfvpLFBXfQkh2eMJAQq24Vx9472lvn5wg0MAO6B+iPfJfzdR9hJYalg==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.3.1", - "@jest/types": "^29.3.1", - "babel-jest": "^29.3.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^29.3.1", - "jest-environment-node": "^29.3.1", - "jest-get-type": "^29.2.0", - "jest-regex-util": "^29.2.0", - "jest-resolve": "^29.3.1", - "jest-runner": "^29.3.1", - "jest-util": "^29.3.1", - "jest-validate": "^29.3.1", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^29.3.1", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "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==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-diff": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.3.1.tgz", - "integrity": "sha512-vU8vyiO7568tmin2lA3r2DP8oRvzhvRcD4DjpXc6uGveQodyk7CKLhQlCSiwgx3g0pFaE88/KLZ0yaTWMc4Uiw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^29.3.1", - "jest-get-type": "^29.2.0", - "pretty-format": "^29.3.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "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==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-docblock": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.2.0.tgz", - "integrity": "sha512-bkxUsxTgWQGbXV5IENmfiIuqZhJcyvF7tU4zJ/7ioTutdz4ToB5Yx6JOFBpgI+TphRY4lhOyCWGNH/QFQh5T6A==", - "dev": true, - "requires": { - "detect-newline": "^3.0.0" - } - }, - "jest-each": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.3.1.tgz", - "integrity": "sha512-qrZH7PmFB9rEzCSl00BWjZYuS1BSOH8lLuC0azQE9lQrAx3PWGKHTDudQiOSwIy5dGAJh7KA0ScYlCP7JxvFYA==", - "dev": true, - "requires": { - "@jest/types": "^29.3.1", - "chalk": "^4.0.0", - "jest-get-type": "^29.2.0", - "jest-util": "^29.3.1", - "pretty-format": "^29.3.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "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==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-environment-node": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.3.1.tgz", - "integrity": "sha512-xm2THL18Xf5sIHoU7OThBPtuH6Lerd+Y1NLYiZJlkE3hbE+7N7r8uvHIl/FkZ5ymKXJe/11SQuf3fv4v6rUMag==", - "dev": true, - "requires": { - "@jest/environment": "^29.3.1", - "@jest/fake-timers": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/node": "*", - "jest-mock": "^29.3.1", - "jest-util": "^29.3.1" - } - }, - "jest-get-type": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.2.0.tgz", - "integrity": "sha512-uXNJlg8hKFEnDgFsrCjznB+sTxdkuqiCL6zMgA75qEbAJjJYTs9XPrvDctrEig2GDow22T/LvHgO57iJhXB/UA==", - "dev": true - }, - "jest-haste-map": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.3.1.tgz", - "integrity": "sha512-/FFtvoG1xjbbPXQLFef+WSU4yrc0fc0Dds6aRPBojUid7qlPqZvxdUBA03HW0fnVHXVCnCdkuoghYItKNzc/0A==", - "dev": true, - "requires": { - "@jest/types": "^29.3.1", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "fsevents": "^2.3.2", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.2.0", - "jest-util": "^29.3.1", - "jest-worker": "^29.3.1", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - } - }, - "jest-leak-detector": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.3.1.tgz", - "integrity": "sha512-3DA/VVXj4zFOPagGkuqHnSQf1GZBmmlagpguxEERO6Pla2g84Q1MaVIB3YMxgUaFIaYag8ZnTyQgiZ35YEqAQA==", - "dev": true, - "requires": { - "jest-get-type": "^29.2.0", - "pretty-format": "^29.3.1" - } - }, - "jest-matcher-utils": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.3.1.tgz", - "integrity": "sha512-fkRMZUAScup3txIKfMe3AIZZmPEjWEdsPJFK3AIy5qRohWqQFg1qrmKfYXR9qEkNc7OdAu2N4KPHibEmy4HPeQ==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "jest-diff": "^29.3.1", - "jest-get-type": "^29.2.0", - "pretty-format": "^29.3.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "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==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-message-util": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.3.1.tgz", - "integrity": "sha512-lMJTbgNcDm5z+6KDxWtqOFWlGQxD6XaYwBqHR8kmpkP+WWWG90I35kdtQHY67Ay5CSuydkTBbJG+tH9JShFCyA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.3.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.3.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "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==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-mock": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.3.1.tgz", - "integrity": "sha512-H8/qFDtDVMFvFP4X8NuOT3XRDzOUTz+FeACjufHzsOIBAxivLqkB1PoLCaJx9iPPQ8dZThHPp/G3WRWyMgA3JA==", - "dev": true, - "requires": { - "@jest/types": "^29.3.1", - "@types/node": "*", - "jest-util": "^29.3.1" - } - }, - "jest-pnp-resolver": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true, - "requires": {} - }, - "jest-regex-util": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.2.0.tgz", - "integrity": "sha512-6yXn0kg2JXzH30cr2NlThF+70iuO/3irbaB4mh5WyqNIvLLP+B6sFdluO1/1RJmslyh/f9osnefECflHvTbwVA==", - "dev": true - }, - "jest-resolve": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.3.1.tgz", - "integrity": "sha512-amXJgH/Ng712w3Uz5gqzFBBjxV8WFLSmNjoreBGMqxgCz5cH7swmBZzgBaCIOsvb0NbpJ0vgaSFdJqMdT+rADw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.3.1", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.3.1", - "jest-validate": "^29.3.1", - "resolve": "^1.20.0", - "resolve.exports": "^1.1.0", - "slash": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "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==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-resolve-dependencies": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.3.1.tgz", - "integrity": "sha512-Vk0cYq0byRw2WluNmNWGqPeRnZ3p3hHmjJMp2dyyZeYIfiBskwq4rpiuGFR6QGAdbj58WC7HN4hQHjf2mpvrLA==", - "dev": true, - "requires": { - "jest-regex-util": "^29.2.0", - "jest-snapshot": "^29.3.1" - } - }, - "jest-runner": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.3.1.tgz", - "integrity": "sha512-oFvcwRNrKMtE6u9+AQPMATxFcTySyKfLhvso7Sdk/rNpbhg4g2GAGCopiInk1OP4q6gz3n6MajW4+fnHWlU3bA==", - "dev": true, - "requires": { - "@jest/console": "^29.3.1", - "@jest/environment": "^29.3.1", - "@jest/test-result": "^29.3.1", - "@jest/transform": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "graceful-fs": "^4.2.9", - "jest-docblock": "^29.2.0", - "jest-environment-node": "^29.3.1", - "jest-haste-map": "^29.3.1", - "jest-leak-detector": "^29.3.1", - "jest-message-util": "^29.3.1", - "jest-resolve": "^29.3.1", - "jest-runtime": "^29.3.1", - "jest-util": "^29.3.1", - "jest-watcher": "^29.3.1", - "jest-worker": "^29.3.1", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "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==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-runtime": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.3.1.tgz", - "integrity": "sha512-jLzkIxIqXwBEOZx7wx9OO9sxoZmgT2NhmQKzHQm1xwR1kNW/dn0OjxR424VwHHf1SPN6Qwlb5pp1oGCeFTQ62A==", - "dev": true, - "requires": { - "@jest/environment": "^29.3.1", - "@jest/fake-timers": "^29.3.1", - "@jest/globals": "^29.3.1", - "@jest/source-map": "^29.2.0", - "@jest/test-result": "^29.3.1", - "@jest/transform": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/node": "*", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.3.1", - "jest-message-util": "^29.3.1", - "jest-mock": "^29.3.1", - "jest-regex-util": "^29.2.0", - "jest-resolve": "^29.3.1", - "jest-snapshot": "^29.3.1", - "jest-util": "^29.3.1", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "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==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-snapshot": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.3.1.tgz", - "integrity": "sha512-+3JOc+s28upYLI2OJM4PWRGK9AgpsMs/ekNryUV0yMBClT9B1DF2u2qay8YxcQd338PPYSFNb0lsar1B49sLDA==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-jsx": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.3.1", - "@jest/transform": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/babel__traverse": "^7.0.6", - "@types/prettier": "^2.1.5", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^29.3.1", - "graceful-fs": "^4.2.9", - "jest-diff": "^29.3.1", - "jest-get-type": "^29.2.0", - "jest-haste-map": "^29.3.1", - "jest-matcher-utils": "^29.3.1", - "jest-message-util": "^29.3.1", - "jest-util": "^29.3.1", - "natural-compare": "^1.4.0", - "pretty-format": "^29.3.1", - "semver": "^7.3.5" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "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==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } - }, - "jest-util": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.3.1.tgz", - "integrity": "sha512-7YOVZaiX7RJLv76ZfHt4nbNEzzTRiMW/IiOG7ZOKmTXmoGBxUDefgMAxQubu6WPVqP5zSzAdZG0FfLcC7HOIFQ==", - "dev": true, - "requires": { - "@jest/types": "^29.3.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "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==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-validate": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.3.1.tgz", - "integrity": "sha512-N9Lr3oYR2Mpzuelp1F8negJR3YE+L1ebk1rYA5qYo9TTY3f9OWdptLoNSPP9itOCBIRBqjt/S5XHlzYglLN67g==", - "dev": true, - "requires": { - "@jest/types": "^29.3.1", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^29.2.0", - "leven": "^3.1.0", - "pretty-format": "^29.3.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "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==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-watcher": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.3.1.tgz", - "integrity": "sha512-RspXG2BQFDsZSRKGCT/NiNa8RkQ1iKAjrO0//soTMWx/QUt+OcxMqMSBxz23PYGqUuWm2+m2mNNsmj0eIoOaFg==", - "dev": true, - "requires": { - "@jest/test-result": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "jest-util": "^29.3.1", - "string-length": "^4.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "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==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-worker": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.3.1.tgz", - "integrity": "sha512-lY4AnnmsEWeiXirAIA0c9SDPbuCBq8IYuDVL8PMm0MZ2PEs2yPvRA/J64QBXuZp7CYKrDM/rmNrc9/i3KJQncw==", - "dev": true, - "requires": { - "@types/node": "*", - "jest-util": "^29.3.1", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true - }, - "json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - }, - "kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true - }, - "leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true - }, - "levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - } - }, - "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 - }, - "loader-runner": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", - "dev": true - }, - "loader-utils": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", - "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "dev": true - }, - "lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true - }, - "lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", - "dev": true - }, - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "requires": { - "yallist": "^3.0.2" - } - }, - "make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, - "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, - "make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, - "requires": { - "tmpl": "1.0.5" - } - }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true - }, - "micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "requires": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - } - }, - "mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true - }, - "mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "requires": { - "mime-db": "1.52.0" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "mrmime": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz", - "integrity": "sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==", - "dev": true - }, - "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 - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "natural-compare-lite": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", - "dev": true - }, - "neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true - }, - "node-fetch": { - "version": "2.6.8", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.8.tgz", - "integrity": "sha512-RZ6dBYuj8dRSfxpUSu+NsdF1dpPpluJxwOp+6IoDp/sH2QNDSvurYsAa+F1WxY2RjA1iP93xhcsUoYbF2XBqVg==", - "requires": { - "whatwg-url": "^5.0.0" - }, - "dependencies": { - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - } - } - }, - "node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true - }, - "node-releases": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.8.tgz", - "integrity": "sha512-dFSmB8fFHEH/s81Xi+Y/15DQY6VHW81nXRj86EMSL3lmuTmK1e+aT4wrFCkTbm+gSwkw4KpX+rT/pMM2c1mF+A==", - "dev": true - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "requires": { - "path-key": "^3.0.0" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "opener": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", - "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", - "dev": true - }, - "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "requires": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0" - } - }, - "parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - } - }, - "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==", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - }, - "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true - }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true - }, - "pirates": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", - "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", - "dev": true - }, - "pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "dev": true, - "requires": { - "find-up": "^3.0.0" - }, - "dependencies": { - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "dev": true - } - } - }, - "prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true - }, - "prettier": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.1.tgz", - "integrity": "sha512-lqGoSJBQNJidqCHE80vqZJHWHRFoNYsSpP9AjFhlhi9ODCJA541svILes/+/1GM3VaL/abZi7cpFzOpdR9UPKg==", - "dev": true - }, - "pretty-format": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.3.1.tgz", - "integrity": "sha512-FyLnmb1cYJV8biEIiRyzRFvs2lry7PPIvOqKVe1GCUEYg4YGmlx1qG9EJNMxArYm7piII4qb8UV1Pncq5dxmcg==", - "dev": true, - "requires": { - "@jest/schemas": "^29.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true - }, - "prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, - "requires": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - } - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true - }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "requires": { - "safe-buffer": "^5.1.0" - } - }, - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "optional": true, - "requires": { - "picomatch": "^2.2.1" - } - }, - "rechoir": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz", - "integrity": "sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==", - "dev": true, - "requires": { - "resolve": "^1.9.0" - } - }, - "regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "dev": true - }, - "regenerate-unicode-properties": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", - "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", - "dev": true, - "requires": { - "regenerate": "^1.4.2" - } - }, - "regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", - "dev": true - }, - "regenerator-transform": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.1.tgz", - "integrity": "sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.8.4" - } - }, - "regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true - }, - "regexpu-core": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.2.2.tgz", - "integrity": "sha512-T0+1Zp2wjF/juXMrMxHxidqGYn8U4R+zleSJhX9tQ1PUsS8a9UtYfbsF9LdiVgNX3kiX8RNaKM42nfSgvFJjmw==", - "dev": true, - "requires": { - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.1.0", - "regjsgen": "^0.7.1", - "regjsparser": "^0.9.1", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.1.0" - } - }, - "regjsgen": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.7.1.tgz", - "integrity": "sha512-RAt+8H2ZEzHeYWxZ3H2z6tF18zyyOnlcdaafLrm21Bguj7uZy6ULibiAFdXEtKQY4Sy7wDTwDiOazasMLc4KPA==", - "dev": true - }, - "regjsparser": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", - "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", - "dev": true, - "requires": { - "jsesc": "~0.5.0" - }, - "dependencies": { - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", - "dev": true - } - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true - }, - "require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true - }, - "resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", - "dev": true, - "requires": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - }, - "resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "requires": { - "resolve-from": "^5.0.0" - } - }, - "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 - }, - "resolve.exports": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.1.tgz", - "integrity": "sha512-/NtpHNDN7jWhAaQ9BvBUYZ6YTXsRBgfqWFWP7BZBaoMJO/I3G5OFzvTuWNlZC3aPjins1F+TNrLKsGbH4rfsRQ==", - "dev": true - }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true - }, - "rfdc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", - "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==" - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "requires": { - "queue-microtask": "^1.2.2" - } - }, - "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==", - "dev": true - }, - "schema-utils": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", - "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.5", - "ajv": "^6.12.4", - "ajv-keywords": "^3.5.2" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - }, - "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, - "requires": { - "randombytes": "^2.1.0" - } - }, - "shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dev": true, - "requires": { - "kind-of": "^6.0.2" - } - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "sirv": { - "version": "1.0.19", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-1.0.19.tgz", - "integrity": "sha512-JuLThK3TnZG1TAKDwNIqNq6QA2afLOCcm+iE8D1Kj3GA40pSPsxQjjJl0J8X3tsR7T+CP1GavpzLwYkgVLWrZQ==", - "dev": true, - "requires": { - "@polka/url": "^1.0.0-next.20", - "mrmime": "^1.0.0", - "totalist": "^1.0.0" - } - }, - "sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true - }, - "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", - "dev": true - }, - "slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "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==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - } - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true - }, - "stack-utils": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "dev": true, - "requires": { - "escape-string-regexp": "^2.0.0" - }, - "dependencies": { - "escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true - } - } - }, - "string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "requires": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - } - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true - }, - "strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true - }, - "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==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true - }, - "table": { - "version": "6.8.1", - "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz", - "integrity": "sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==", - "dev": true, - "requires": { - "ajv": "^8.0.1", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, - "dependencies": { - "ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - } - } - }, - "tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true - }, - "terser": { - "version": "5.16.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.16.1.tgz", - "integrity": "sha512-xvQfyfA1ayT0qdK47zskQgRZeWLoOQ8JQ6mIgRGVNwZKdQMU+5FkCBjmv4QjcrTzyZquRw2FVtlJSRUmMKQslw==", - "dev": true, - "requires": { - "@jridgewell/source-map": "^0.3.2", - "acorn": "^8.5.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "dependencies": { - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - } - } - }, - "terser-webpack-plugin": { - "version": "5.3.6", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.6.tgz", - "integrity": "sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "^0.3.14", - "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.0", - "terser": "^5.14.1" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dev": true, - "requires": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - } - }, - "schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - } - }, - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "requires": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, - "tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true - }, - "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==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "totalist": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/totalist/-/totalist-1.1.0.tgz", - "integrity": "sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g==", - "dev": true - }, - "ts-jest": { - "version": "29.0.5", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.0.5.tgz", - "integrity": "sha512-PL3UciSgIpQ7f6XjVOmbi96vmDHUqAyqDr8YxzopDqX3kfgYtX1cuNeBjP+L9sFXi6nzsGGA6R3fP3DDDJyrxA==", - "dev": true, - "requires": { - "bs-logger": "0.x", - "fast-json-stable-stringify": "2.x", - "jest-util": "^29.0.0", - "json5": "^2.2.3", - "lodash.memoize": "4.x", - "make-error": "1.x", - "semver": "7.x", - "yargs-parser": "^21.0.1" - }, - "dependencies": { - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } - }, - "ts-loader": { - "version": "9.4.2", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.2.tgz", - "integrity": "sha512-OmlC4WVmFv5I0PpaxYb+qGeGOdm5giHU7HwDDUjw59emP2UYMHy9fFSDcYgSNoH8sXcj4hGCSEhlDZ9ULeDraA==", - "dev": true, - "requires": { - "chalk": "^4.1.0", - "enhanced-resolve": "^5.0.0", - "micromatch": "^4.0.0", - "semver": "^7.3.4" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "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==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } - }, - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "requires": { - "tslib": "^1.8.1" - } - }, - "type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1" - } - }, - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true - }, - "type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true - }, - "typescript": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", - "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", - "dev": true - }, - "unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", - "dev": true - }, - "unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "dev": true, - "requires": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - } - }, - "unicode-match-property-value-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", - "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", - "dev": true - }, - "unicode-property-aliases-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", - "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", - "dev": true - }, - "update-browserslist-db": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", - "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", - "dev": true, - "requires": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - } - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "v8-to-istanbul": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz", - "integrity": "sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0" - } - }, - "walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dev": true, - "requires": { - "makeerror": "1.0.12" - } - }, - "watchpack": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", - "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", - "dev": true, - "requires": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - } - }, - "webpack": { - "version": "5.76.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.0.tgz", - "integrity": "sha512-l5sOdYBDunyf72HW8dF23rFtWq/7Zgvt/9ftMof71E/yUb1YLOBmTgA2K4vQthB3kotMrSj609txVE0dnr2fjA==", - "dev": true, - "requires": { - "@types/eslint-scope": "^3.7.3", - "@types/estree": "^0.0.51", - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/wasm-edit": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "acorn": "^8.7.1", - "acorn-import-assertions": "^1.7.6", - "browserslist": "^4.14.5", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.10.0", - "es-module-lexer": "^0.9.0", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^3.1.0", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.1.3", - "watchpack": "^2.4.0", - "webpack-sources": "^3.2.3" - }, - "dependencies": { - "acorn-import-assertions": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", - "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", - "dev": true, - "requires": {} - }, - "schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - } - } - } - }, - "webpack-bundle-analyzer": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.7.0.tgz", - "integrity": "sha512-j9b8ynpJS4K+zfO5GGwsAcQX4ZHpWV+yRiHDiL+bE0XHJ8NiPYLTNVQdlFYWxtpg9lfAQNlwJg16J9AJtFSXRg==", - "dev": true, - "requires": { - "acorn": "^8.0.4", - "acorn-walk": "^8.0.0", - "chalk": "^4.1.0", - "commander": "^7.2.0", - "gzip-size": "^6.0.0", - "lodash": "^4.17.20", - "opener": "^1.5.2", - "sirv": "^1.0.7", - "ws": "^7.3.1" - }, - "dependencies": { - "acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "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==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "webpack-cli": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz", - "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==", - "dev": true, - "requires": { - "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^1.2.0", - "@webpack-cli/info": "^1.5.0", - "@webpack-cli/serve": "^1.7.0", - "colorette": "^2.0.14", - "commander": "^7.0.0", - "cross-spawn": "^7.0.3", - "fastest-levenshtein": "^1.0.12", - "import-local": "^3.0.2", - "interpret": "^2.2.0", - "rechoir": "^0.7.0", - "webpack-merge": "^5.7.3" - }, - "dependencies": { - "commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "dev": true - } - } - }, - "webpack-merge": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", - "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==", - "dev": true, - "requires": { - "clone-deep": "^4.0.1", - "wildcard": "^2.0.0" - } - }, - "webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", - "dev": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "wildcard": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", - "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", - "dev": true - }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true - }, - "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==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "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==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - } - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "write-file-atomic": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dev": true, - "requires": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - } - }, - "ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", - "dev": true, - "requires": {} - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - }, - "yargs": { - "version": "17.6.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz", - "integrity": "sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==", - "dev": true, - "requires": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - } - }, - "yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true - }, - "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==", - "dev": true - } - } + "name": "@absmartly/javascript-sdk", + "version": "2.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@absmartly/javascript-sdk", + "version": "2.0.0", + "license": "Apache-2.0", + "devDependencies": { + "@vitest/coverage-v8": "^3.0.0", + "tsup": "^8.0.0", + "tsx": "^4.0.0", + "typescript": "^5.4.0", + "vitest": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "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": ">=18" + } + }, + "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": ">=18" + } + }, + "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, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "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, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "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": ">=18" + } + }, + "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, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "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, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "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": ">=18" + } + }, + "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, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "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, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "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, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "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, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "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": ">=18" + } + }, + "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": ">=18" + } + }, + "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, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "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, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "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, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "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, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "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/@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": ">=18" + } + }, + "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/@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": ">=18" + } + }, + "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/@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": ">=18" + } + }, + "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": ">=18" + } + }, + "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, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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": { + "@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": ">=6.0.0" + } + }, + "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, + "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/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "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/@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, + "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" + ], + "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/@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, + "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/@vitest/coverage-v8": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.4.tgz", + "integrity": "sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "@bcoe/v8-coverage": "^1.0.2", + "ast-v8-to-istanbul": "^0.3.3", + "debug": "^4.4.1", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^5.0.6", + "istanbul-reports": "^3.1.7", + "magic-string": "^0.30.17", + "magicast": "^0.3.5", + "std-env": "^3.9.0", + "test-exclude": "^7.0.1", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "3.2.4", + "vitest": "3.2.4" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "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": { + "@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://opencollective.com/vitest" + } + }, + "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": { + "@vitest/spy": "3.2.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "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/@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, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "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, + "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/@vitest/snapshot": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", + "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "dev": true, + "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/@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": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "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": { + "@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": ">=0.4.0" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "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": ">=12" + } + }, + "node_modules/ast-v8-to-istanbul": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.12.tgz", + "integrity": "sha512-BRRC8VRZY2R4Z4lFIL35MwNXmwVqBityvOIwETtsCSwvjl0IdgFsy9NhdaA6j74nUdtJJlIypeRhpDam19Wq3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "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": ">=8" + } + }, + "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": { + "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": ">=18" + } + }, + "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": ">= 16" + } + }, + "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": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "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, + "license": "MIT" + }, + "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": ">= 6" + } + }, + "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": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "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" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "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/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "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": { + "@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" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "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": { + "magic-string": "^0.30.17", + "mlly": "^1.7.4", + "rollup": "^4.34.8" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.7", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.7.tgz", + "integrity": "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "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", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob/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, + "license": "MIT" + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "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/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, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "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, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "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": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magicast": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", + "source-map-js": "^1.2.0" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "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": { + "acorn": "^8.16.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.3" + } + }, + "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, + "license": "MIT" + }, + "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": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "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, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "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": ">=0.10.0" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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": ">= 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": "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": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "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, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "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": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "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": { + "lilconfig": "^3.1.1" + }, + "engines": { + "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/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": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "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" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "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": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "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/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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/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/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/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, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/strip-literal/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, + "license": "MIT" + }, + "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": { + "@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": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.2.tgz", + "integrity": "sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^10.2.2" + }, + "engines": { + "node": ">=18" + } + }, + "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": { + "any-promise": "^1.0.0" + } + }, + "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/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "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": "^18.0.0 || >=20.0.0" + } + }, + "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": ">=14.0.0" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "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, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "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/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": ">=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/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "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, + "license": "MIT" + }, + "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": { + "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": "^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/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": { + "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": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "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": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "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/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "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" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/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==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/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, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + } + } } diff --git a/package.json b/package.json index 380e9ad..c3cb6a5 100644 --- a/package.json +++ b/package.json @@ -1,93 +1,65 @@ { - "name": "@absmartly/javascript-sdk", - "version": "1.13.4", - "description": "A/B Smartly Javascript SDK", - "homepage": "https://github.com/absmartly/javascript-sdk#README.md", - "bugs": "https://github.com/absmartly/javascript-sdk/issues", - "keywords": [ - "absmartly", - "ab-smartly", - "a/b-smartly", - "ab-testing", - "a/b-testing", - "split-testing", - "ab", - "a/b", - "cro" - ], - "license": "Apache-2.0", - "main": "lib/index.js", - "module": "es/index.js", - "browser": "dist/absmartly.min.js", - "types": "types/index.d.ts", - "engines": { - "npm": ">=3", - "node": ">=6" - }, - "scripts": { - "build-browser": "TARGET=browser webpack --progress --config webpack.config.js && TARGET=browser NODE_ENV=production webpack --progress --config webpack.config.js", - "build-cjs": "TARGET=cjs babel js --delete-dir-on-start --ignore 'browser.js' -d lib", - "build-es": "TARGET=es babel js --delete-dir-on-start --ignore 'browser.js' -d es", - "build": "npm run -s format && npm run -s lint && npm run -s generate-version && npm run -s compile && npm run -s test && npm run -s build-es && npm run -s build-cjs && npm run -s build-browser", - "lint": "eslint -f stylish 'src/**/*.{js,mjs,jsx,ts,mts,tsx}'", - "format": "prettier --write '**/*.{js,mjs,jsx,json,ts,mts,tsx}'", - "test": "jest --coverage", - "prepack": "npm run -s build", - "compile": "tsc", - "generate-version": "node scripts/generate-version.js" - }, - "dependencies": { - "node-fetch": "^2.6.7", - "rfdc": "^1.3.0", - "core-js": "^3.20.0" - }, - "devDependencies": { - "@babel/cli": "^7.17.3", - "@babel/core": "^7.17.4", - "@babel/eslint-parser": "^7.17.0", - "@babel/plugin-proposal-class-properties": "^7.16.7", - "@babel/plugin-proposal-export-default-from": "^7.16.7", - "@babel/plugin-proposal-export-namespace-from": "^7.16.7", - "@babel/plugin-proposal-logical-assignment-operators": "^7.16.7", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.7", - "@babel/plugin-proposal-numeric-separator": "^7.16.7", - "@babel/plugin-proposal-optional-chaining": "^7.16.7", - "@babel/plugin-proposal-throw-expressions": "^7.16.7", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-transform-runtime": "^7.16.7", - "@babel/preset-env": "^7.16.11", - "@babel/preset-typescript": "^7.18.6", - "@babel/register": "^7.17.0", - "@types/jest": "^29.2.5", - "@types/node-fetch": "^2.6.2", - "@typescript-eslint/eslint-plugin": "^5.48.2", - "@typescript-eslint/parser": "^5.48.2", - "babel-jest": "^29", - "babel-loader": "^8.2.3", - "eslint": "^7.32.0", - "eslint-config-prettier": "^7.1.0", - "jest": "^29.3.1", - "prettier": "^2.4.1", - "terser-webpack-plugin": "^5.3.1", - "ts-jest": "^29.0.5", - "ts-loader": "^9.4.2", - "typescript": "^4.9.4", - "webpack": "^5.60.0", - "webpack-bundle-analyzer": "^4.5.0", - "webpack-cli": "^4.9.1" - }, - "publishConfig": { - "access": "public" - }, - "files": [ - "README.md", - "CONTRIBUTING.md", - "LICENSE", - "package.json", - "dist/", - "es/", - "lib/", - "types/" - ] + "name": "@absmartly/javascript-sdk", + "version": "2.0.0", + "description": "A/B Smartly Javascript SDK", + "homepage": "https://github.com/absmartly/javascript-sdk#README.md", + "bugs": "https://github.com/absmartly/javascript-sdk/issues", + "keywords": [ + "absmartly", + "ab-smartly", + "a/b-smartly", + "ab-testing", + "a/b-testing", + "split-testing", + "ab", + "a/b", + "cro" + ], + "license": "Apache-2.0", + "type": "module", + "main": "dist/index.cjs", + "module": "dist/index.js", + "browser": "dist/index.global.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" + } + } + }, + "engines": { + "node": ">=18" + }, + "scripts": { + "build": "npm run generate-version && tsup", + "test": "vitest run", + "test:watch": "vitest", + "test:coverage": "vitest run --coverage", + "typecheck": "tsc --noEmit", + "lint": "tsc --noEmit", + "generate-version": "tsx scripts/generate-version.ts", + "prepack": "npm run build" + }, + "devDependencies": { + "tsup": "^8.0.0", + "typescript": "^5.4.0", + "vitest": "^3.0.0", + "tsx": "^4.0.0", + "@vitest/coverage-v8": "^3.0.0" + }, + "publishConfig": { + "access": "public" + }, + "files": [ + "README.md", + "LICENSE", + "package.json", + "dist/" + ] } diff --git a/scripts/generate-version.ts b/scripts/generate-version.ts new file mode 100644 index 0000000..ef29a41 --- /dev/null +++ b/scripts/generate-version.ts @@ -0,0 +1,4 @@ +import { readFileSync, writeFileSync } from "node:fs"; + +const pkg = JSON.parse(readFileSync("package.json", "utf-8")); +writeFileSync("src/version.ts", `export const SDK_VERSION = "${pkg.version}";\n`); diff --git a/src/__tests__/placeholder.test.ts b/src/__tests__/placeholder.test.ts new file mode 100644 index 0000000..ec4f8cf --- /dev/null +++ b/src/__tests__/placeholder.test.ts @@ -0,0 +1,5 @@ +import { expect, test } from "vitest"; + +test("placeholder", () => { + expect(true).toBe(true); +}); diff --git a/src/index.ts b/src/index.ts index aa43221..1bda49b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1 @@ -import Context from "./context"; -import SDK from "./sdk"; -import { mergeConfig } from "./config"; -import { ContextDataProvider } from "./provider"; -import { ContextPublisher } from "./publisher"; -// eslint-disable-next-line no-shadow -import { AbortController } from "./abort"; - -export { mergeConfig, AbortController, Context, ContextDataProvider, ContextPublisher, SDK }; -export default { mergeConfig, AbortController, Context, ContextDataProvider, ContextPublisher, SDK }; +export const placeholder = true; diff --git a/src/version.ts b/src/version.ts index 9bcd66f..478f50d 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const SDK_VERSION = "1.13.4"; +export const SDK_VERSION = "2.0.0"; diff --git a/tsconfig.json b/tsconfig.json index acd4126..5a2ac7e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,32 +1,25 @@ { - "compilerOptions": { - "target": "es2017", - "module": "commonjs", - "lib": ["dom", "es6", "es2017", "esnext.asynciterable"], - "skipLibCheck": true, - "sourceMap": true, - "outDir": "./js", - "allowJs": true, - "declaration": true, - "declarationMap": true, - "declarationDir": "./types", - "isolatedModules": true, - "moduleResolution": "node", - "removeComments": true, - "noImplicitAny": true, - "strictNullChecks": true, - "strictFunctionTypes": true, - "noImplicitThis": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noImplicitReturns": true, - "allowSyntheticDefaultImports": true, - "esModuleInterop": true, - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "resolveJsonModule": true, - "baseUrl": "." - }, - "exclude": ["node_modules"], - "include": ["./src/**/*.ts"] + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "lib": ["ES2022", "DOM"], + "strict": true, + "skipLibCheck": true, + "sourceMap": true, + "declaration": true, + "declarationMap": true, + "outDir": "./dist", + "rootDir": "./src", + "isolatedModules": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "noUncheckedIndexedAccess": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.test.ts", "node_modules"] } diff --git a/tsup.config.ts b/tsup.config.ts new file mode 100644 index 0000000..adcd306 --- /dev/null +++ b/tsup.config.ts @@ -0,0 +1,16 @@ +import { defineConfig } from "tsup"; + +export default defineConfig({ + entry: ["src/index.ts"], + format: ["esm", "cjs", "iife"], + globalName: "absmartly", + dts: true, + sourcemap: true, + clean: true, + minify: true, + target: "es2022", + outExtension({ format }) { + if (format === "iife") return { js: ".global.js" }; + return {}; + }, +}); diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..f9ae823 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + globals: true, + environment: "node", + coverage: { + provider: "v8", + include: ["src/**/*.ts"], + exclude: ["src/**/*.test.ts", "src/version.ts"], + }, + }, +}); diff --git a/webpack.config.js b/webpack.config.js deleted file mode 100644 index 15ff2da..0000000 --- a/webpack.config.js +++ /dev/null @@ -1,55 +0,0 @@ -const TerserPlugin = require("terser-webpack-plugin"); -const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin; - -const env = process.env.NODE_ENV || "development"; -const analyze = process.env.WEBPACK_ANALYZE || false; - -module.exports = function () { - const config = { - devtool: "source-map", - entry: { - absmartly: ["./js/browser.js"], - }, - - target: "browserslist", - - output: { - library: "absmartly", - libraryTarget: "umd", - libraryExport: "default", - }, - - module: { - rules: [ - { - test: /\.js$/, - exclude: /node_modules/, - use: ["babel-loader"], - }, - ], - }, - - mode: env, - }; - - if (env === "production") { - config.output.filename = "[name].min.js"; - config.performance = { - hints: "error", - maxAssetSize: 131072, - }; - - config.optimization = { - minimize: true, - minimizer: [new TerserPlugin()], - }; - - if (analyze) { - config.plugins = [new BundleAnalyzerPlugin()]; - } - } else { - config.output.filename = "[name].js"; - } - - return config; -}; From f8816417436a985df7800b4d954950ba8db3ce5b Mon Sep 17 00:00:00 2001 From: Jonas Alves Date: Sat, 28 Mar 2026 16:58:43 +0000 Subject: [PATCH 02/22] feat: add shared type definitions and custom error classes --- src/__tests__/errors.test.ts | 40 ++++++++++ src/types.ts | 146 +++++++++++++++++++++++++++++++++++ 2 files changed, 186 insertions(+) create mode 100644 src/__tests__/errors.test.ts create mode 100644 src/types.ts diff --git a/src/__tests__/errors.test.ts b/src/__tests__/errors.test.ts new file mode 100644 index 0000000..e9b009e --- /dev/null +++ b/src/__tests__/errors.test.ts @@ -0,0 +1,40 @@ +import { describe, expect, test } from "vitest"; +import { AbortError, RetryError, TimeoutError } from "../errors"; + +describe("TimeoutError", () => { + test("has correct name, message, and timeout", () => { + const error = new TimeoutError(3000); + expect(error).toBeInstanceOf(Error); + expect(error).toBeInstanceOf(TimeoutError); + expect(error.name).toBe("TimeoutError"); + expect(error.message).toBe("Timeout exceeded."); + expect(error.timeout).toBe(3000); + }); +}); + +describe("RetryError", () => { + test("has correct name, message, retries, and exception", () => { + const cause = new Error("connection refused"); + const error = new RetryError(5, cause, "https://example.com/api"); + expect(error).toBeInstanceOf(Error); + expect(error).toBeInstanceOf(RetryError); + expect(error.name).toBe("RetryError"); + expect(error.message).toBe("Retries exhausted. URL: https://example.com/api - Last Error: connection refused"); + expect(error.retries).toBe(5); + expect(error.exception).toBe(cause); + }); +}); + +describe("AbortError", () => { + test("has correct name and default message", () => { + const error = new AbortError(); + expect(error).toBeInstanceOf(Error); + expect(error).toBeInstanceOf(AbortError); + expect(error.name).toBe("AbortError"); + }); + + test("accepts custom message", () => { + const error = new AbortError("user cancelled"); + expect(error.message).toBe("user cancelled"); + }); +}); diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..12fbdb6 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,146 @@ +export type JSONPrimitive = string | number | boolean | null; +export type JSONObject = { [key: string]: JSONValue }; +export type JSONArray = JSONValue[]; +export type JSONValue = JSONPrimitive | JSONObject | JSONArray; + +export type CustomFieldValueType = "text" | "string" | "number" | "json" | "boolean"; + +export type CustomFieldValue = { + name: string; + value: string; + type: CustomFieldValueType; +}; + +export type ExperimentData = { + id: number; + name: string; + unitType: string | null; + iteration: number; + fullOnVariant: number; + trafficSplit: number[]; + trafficSeedHi: number; + trafficSeedLo: number; + audience: string; + audienceStrict: boolean; + split: number[]; + seedHi: number; + seedLo: number; + variants: { config: null | string }[]; + variables: Record; + variant: number; + overridden: boolean; + assigned: boolean; + exposed: boolean; + eligible: boolean; + fullOn: boolean; + custom: boolean; + audienceMismatch: boolean; + customFieldValues: CustomFieldValue[] | null; +}; + +export type Assignment = { + id: number; + iteration: number; + fullOnVariant: number; + unitType: string | null; + variant: number; + overridden: boolean; + assigned: boolean; + exposed: boolean; + eligible: boolean; + fullOn: boolean; + custom: boolean; + audienceMismatch: boolean; + trafficSplit?: number[]; + variables?: Record; + attrsSeq?: number; +}; + +export type Experiment = { + data: ExperimentData; + variables: Record[]; +}; + +export type Unit = { + type: string; + uid: string | null; +}; + +export type Exposure = { + id: number; + name: string; + exposedAt: number; + unit: string | null; + variant: number; + assigned: boolean; + eligible: boolean; + overridden: boolean; + fullOn: boolean; + custom: boolean; + audienceMismatch: boolean; +}; + +export type Attribute = { + name: string; + value: unknown; + setAt: number; +}; + +export type Units = Record; + +export type Goal = { + name: string; + properties: Record | null; + achievedAt: number; +}; + +export type ContextParams = { + units: Record; +}; + +export type ContextData = { + experiments?: ExperimentData[]; +}; + +export type ApplicationObject = { name: string; version: number | string }; + +export type ClientRequestOptions = { + query?: Record; + path: string; + method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS"; + body?: Record; + auth?: boolean; + signal?: AbortSignal; + timeout?: number; +}; + +export type ClientOptions = { + agent?: string; + apiKey: string; + application: string | ApplicationObject; + endpoint: string; + environment: string; + retries?: number; + timeout?: number; + keepalive?: boolean; +}; + +export type NormalizedClientOptions = Omit, "application"> & { + application: ApplicationObject; +}; + +export type PublishParams = { + units: Unit[]; + publishedAt: number; + hashed: boolean; + sdkVersion: string; + attributes?: Attribute[]; + goals?: Goal[]; + exposures?: Exposure[]; +}; + +export type EventName = "error" | "ready" | "refresh" | "publish" | "exposure" | "goal" | "finalize"; + +export type EventLoggerData = Error | Exposure | Goal | ContextData | PublishParams; + +export type EventLogger = (context: unknown, eventName: EventName, data?: EventLoggerData) => void; From ce6bb5321b13940b34750ebe0146b9977e8d1c5b Mon Sep 17 00:00:00 2001 From: Jonas Alves Date: Sat, 28 Mar 2026 17:23:18 +0000 Subject: [PATCH 03/22] feat: add murmur3_32 hash implementation --- src/__tests__/murmur3.test.ts | 51 +++++++++++++++++++++++++++++++ src/murmur3.ts | 56 +++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 src/__tests__/murmur3.test.ts create mode 100644 src/murmur3.ts diff --git a/src/__tests__/murmur3.test.ts b/src/__tests__/murmur3.test.ts new file mode 100644 index 0000000..7f76321 --- /dev/null +++ b/src/__tests__/murmur3.test.ts @@ -0,0 +1,51 @@ +import { describe, expect, test } from "vitest"; +import { murmur3_32 } from "../murmur3"; +import { stringToUint8Array } from "../hashing"; + +describe("murmur3_32", () => { + const testCases: [string, number, number][] = [ + ["", 0x00000000, 0x00000000], + [" ", 0x00000000, 0x7ef49b98], + ["t", 0x00000000, 0xca87df4d], + ["te", 0x00000000, 0xedb8ee1b], + ["tes", 0x00000000, 0x0bb90e5a], + ["test", 0x00000000, 0xba6bd213], + ["testy", 0x00000000, 0x44af8342], + ["testy1", 0x00000000, 0x8a1a243a], + ["testy12", 0x00000000, 0x845461b9], + ["testy123", 0x00000000, 0x47628ac4], + ["special characters a\u00e7b\u2193c", 0x00000000, 0xbe83b140], + ["The quick brown fox jumps over the lazy dog", 0x00000000, 0x2e4ff723], + ["", 0xdeadbeef, 0x0de5c6a9], + [" ", 0xdeadbeef, 0x25acce43], + ["t", 0xdeadbeef, 0x3b15dcf8], + ["te", 0xdeadbeef, 0xac981332], + ["tes", 0xdeadbeef, 0xc1c78dda], + ["test", 0xdeadbeef, 0xaa22d41a], + ["testy", 0xdeadbeef, 0x84f5f623], + ["testy1", 0xdeadbeef, 0x09ed28e9], + ["testy12", 0xdeadbeef, 0x22467835], + ["testy123", 0xdeadbeef, 0xd633060d], + ["special characters a\u00e7b\u2193c", 0xdeadbeef, 0xf7fdd8a2], + ["The quick brown fox jumps over the lazy dog", 0xdeadbeef, 0x3a7b3f4d], + ["", 0x00000001, 0x514e28b7], + [" ", 0x00000001, 0x4f0f7132], + ["t", 0x00000001, 0x5db1831e], + ["te", 0x00000001, 0xd248bb2e], + ["tes", 0x00000001, 0xd432eb74], + ["test", 0x00000001, 0x99c02ae2], + ["testy", 0x00000001, 0xc5b2dc1e], + ["testy1", 0x00000001, 0x33925ceb], + ["testy12", 0x00000001, 0xd92c9f23], + ["testy123", 0x00000001, 0x3bc1712d], + ["special characters a\u00e7b\u2193c", 0x00000001, 0x293327b5], + ["The quick brown fox jumps over the lazy dog", 0x00000001, 0x78e69e27], + ]; + + for (const [input, seed, expected] of testCases) { + test(`murmur3_32("${input}", 0x${seed.toString(16)}) == 0x${expected.toString(16).padStart(8, "0")}`, () => { + const bytes = stringToUint8Array(input); + expect(murmur3_32(bytes.buffer, seed)).toBe(expected); + }); + } +}); diff --git a/src/murmur3.ts b/src/murmur3.ts new file mode 100644 index 0000000..511853f --- /dev/null +++ b/src/murmur3.ts @@ -0,0 +1,56 @@ +const C1 = 0xcc9e2d51; +const C2 = 0x1b873593; +const C3 = 0xe6546b64; + +const imul32 = Math.imul; + +function fmix32(h: number): number { + h ^= h >>> 16; + h = imul32(h, 0x85ebca6b); + h ^= h >>> 13; + h = imul32(h, 0xc2b2ae35); + h ^= h >>> 16; + return h >>> 0; +} + +function rotl32(a: number, b: number): number { + return (a << b) | (a >>> (32 - b)); +} + +function scramble32(block: number): number { + return imul32(rotl32(imul32(block, C1), 15), C2); +} + +export function murmur3_32(key: ArrayBufferLike, hash?: number): number { + hash = (hash || 0) >>> 0; + const dataView = new DataView(key); + + let i: number; + const n = dataView.byteLength & ~3; + for (i = 0; i < n; i += 4) { + const chunk = dataView.getUint32(i, true); + hash ^= scramble32(chunk); + hash = rotl32(hash, 13); + hash = imul32(hash, 5) + C3; + } + + let remaining = 0; + switch (dataView.byteLength & 3) { + case 3: + remaining ^= dataView.getUint8(i + 2) << 16; + // fallthrough + case 2: + remaining ^= dataView.getUint8(i + 1) << 8; + // fallthrough + case 1: + remaining ^= dataView.getUint8(i); + hash ^= scramble32(remaining); + // fallthrough + default: + break; + } + + hash ^= dataView.byteLength; + hash = fmix32(hash); + return hash >>> 0; +} From 8f6a7af78e0859a0e19ce7e0fac827d1919ee2c6 Mon Sep 17 00:00:00 2001 From: Jonas Alves Date: Sat, 28 Mar 2026 17:23:18 +0000 Subject: [PATCH 04/22] feat: add MD5 hash implementation --- src/__tests__/md5.test.ts | 45 ++++++++++ src/md5.ts | 172 +++++++++++++++++++------------------- 2 files changed, 130 insertions(+), 87 deletions(-) create mode 100644 src/__tests__/md5.test.ts diff --git a/src/__tests__/md5.test.ts b/src/__tests__/md5.test.ts new file mode 100644 index 0000000..96ad237 --- /dev/null +++ b/src/__tests__/md5.test.ts @@ -0,0 +1,45 @@ +import { describe, expect, test } from "vitest"; +import { md5 } from "../md5"; + +function stringToBuffer(value: string): ArrayBuffer { + const n = value.length; + const array: number[] = []; + let k = 0; + for (let i = 0; i < n; ++i) { + const c = value.charCodeAt(i); + if (c < 0x80) { + array[k++] = c; + } else if (c < 0x800) { + array[k++] = (c >> 6) | 192; + array[k++] = (c & 63) | 128; + } else { + array[k++] = (c >> 12) | 224; + array[k++] = ((c >> 6) & 63) | 128; + array[k++] = (c & 63) | 128; + } + } + return Uint8Array.from(array).buffer; +} + +function toHex(bytes: Uint8Array): string { + return Array.from(bytes) + .map((b) => b.toString(16).padStart(2, "0")) + .join(""); +} + +describe("md5", () => { + const testCases: [string, string][] = [ + ["", "d41d8cd98f00b204e9800998ecf8427e"], + ["a", "0cc175b9c0f1b6a831c399e269772661"], + ["abc", "900150983cd24fb0d6963f7d28e17f72"], + ["message digest", "f96b697d7cb7938d525a2f31aaf161d0"], + ["abcdefghijklmnopqrstuvwxyz", "c3fcd3d76192e4007dfb496cca67e13b"], + ]; + + for (const [input, expectedHex] of testCases) { + test(`md5("${input}") == ${expectedHex}`, () => { + const result = md5(stringToBuffer(input)); + expect(toHex(result)).toBe(expectedHex); + }); + } +}); diff --git a/src/md5.ts b/src/md5.ts index f1b2e07..5ed0d48 100644 --- a/src/md5.ts +++ b/src/md5.ts @@ -1,108 +1,108 @@ -function cmn(q: number, a: number, b: number, x: number, s: number, t: number) { +function cmn(q: number, a: number, b: number, x: number, s: number, t: number): number { a = a + q + (x >>> 0) + t; return ((a << s) | (a >>> (32 - s))) + b; } -function ff(a: number, b: number, c: number, d: number, x: number, s: number, t: number) { +function ff(a: number, b: number, c: number, d: number, x: number, s: number, t: number): number { return cmn((b & c) | (~b & d), a, b, x, s, t); } -function gg(a: number, b: number, c: number, d: number, x: number, s: number, t: number) { +function gg(a: number, b: number, c: number, d: number, x: number, s: number, t: number): number { return cmn((b & d) | (c & ~d), a, b, x, s, t); } -function hh(a: number, b: number, c: number, d: number, x: number, s: number, t: number) { +function hh(a: number, b: number, c: number, d: number, x: number, s: number, t: number): number { return cmn(b ^ c ^ d, a, b, x, s, t); } -function ii(a: number, b: number, c: number, d: number, x: number, s: number, t: number) { +function ii(a: number, b: number, c: number, d: number, x: number, s: number, t: number): number { return cmn(c ^ (b | ~d), a, b, x, s, t); } -function md5cycle(x: Uint32Array, k: Uint32Array) { - let a = x[0]; - let b = x[1]; - let c = x[2]; - let d = x[3]; - - a = ff(a, b, c, d, k[0], 7, -680876936); - d = ff(d, a, b, c, k[1], 12, -389564586); - c = ff(c, d, a, b, k[2], 17, 606105819); - b = ff(b, c, d, a, k[3], 22, -1044525330); - a = ff(a, b, c, d, k[4], 7, -176418897); - d = ff(d, a, b, c, k[5], 12, 1200080426); - c = ff(c, d, a, b, k[6], 17, -1473231341); - b = ff(b, c, d, a, k[7], 22, -45705983); - a = ff(a, b, c, d, k[8], 7, 1770035416); - d = ff(d, a, b, c, k[9], 12, -1958414417); - c = ff(c, d, a, b, k[10], 17, -42063); - b = ff(b, c, d, a, k[11], 22, -1990404162); - a = ff(a, b, c, d, k[12], 7, 1804603682); - d = ff(d, a, b, c, k[13], 12, -40341101); - c = ff(c, d, a, b, k[14], 17, -1502002290); - b = ff(b, c, d, a, k[15], 22, 1236535329); - - a = gg(a, b, c, d, k[1], 5, -165796510); - d = gg(d, a, b, c, k[6], 9, -1069501632); - c = gg(c, d, a, b, k[11], 14, 643717713); - b = gg(b, c, d, a, k[0], 20, -373897302); - a = gg(a, b, c, d, k[5], 5, -701558691); - d = gg(d, a, b, c, k[10], 9, 38016083); - c = gg(c, d, a, b, k[15], 14, -660478335); - b = gg(b, c, d, a, k[4], 20, -405537848); - a = gg(a, b, c, d, k[9], 5, 568446438); - d = gg(d, a, b, c, k[14], 9, -1019803690); - c = gg(c, d, a, b, k[3], 14, -187363961); - b = gg(b, c, d, a, k[8], 20, 1163531501); - a = gg(a, b, c, d, k[13], 5, -1444681467); - d = gg(d, a, b, c, k[2], 9, -51403784); - c = gg(c, d, a, b, k[7], 14, 1735328473); - b = gg(b, c, d, a, k[12], 20, -1926607734); - - a = hh(a, b, c, d, k[5], 4, -378558); - d = hh(d, a, b, c, k[8], 11, -2022574463); - c = hh(c, d, a, b, k[11], 16, 1839030562); - b = hh(b, c, d, a, k[14], 23, -35309556); - a = hh(a, b, c, d, k[1], 4, -1530992060); - d = hh(d, a, b, c, k[4], 11, 1272893353); - c = hh(c, d, a, b, k[7], 16, -155497632); - b = hh(b, c, d, a, k[10], 23, -1094730640); - a = hh(a, b, c, d, k[13], 4, 681279174); - d = hh(d, a, b, c, k[0], 11, -358537222); - c = hh(c, d, a, b, k[3], 16, -722521979); - b = hh(b, c, d, a, k[6], 23, 76029189); - a = hh(a, b, c, d, k[9], 4, -640364487); - d = hh(d, a, b, c, k[12], 11, -421815835); - c = hh(c, d, a, b, k[15], 16, 530742520); - b = hh(b, c, d, a, k[2], 23, -995338651); - - a = ii(a, b, c, d, k[0], 6, -198630844); - d = ii(d, a, b, c, k[7], 10, 1126891415); - c = ii(c, d, a, b, k[14], 15, -1416354905); - b = ii(b, c, d, a, k[5], 21, -57434055); - a = ii(a, b, c, d, k[12], 6, 1700485571); - d = ii(d, a, b, c, k[3], 10, -1894986606); - c = ii(c, d, a, b, k[10], 15, -1051523); - b = ii(b, c, d, a, k[1], 21, -2054922799); - a = ii(a, b, c, d, k[8], 6, 1873313359); - d = ii(d, a, b, c, k[15], 10, -30611744); - c = ii(c, d, a, b, k[6], 15, -1560198380); - b = ii(b, c, d, a, k[13], 21, 1309151649); - a = ii(a, b, c, d, k[4], 6, -145523070); - d = ii(d, a, b, c, k[11], 10, -1120210379); - c = ii(c, d, a, b, k[2], 15, 718787259); - b = ii(b, c, d, a, k[9], 21, -343485551); - - x[0] = (a + x[0]) >>> 0; - x[1] = (b + x[1]) >>> 0; - x[2] = (c + x[2]) >>> 0; - x[3] = (d + x[3]) >>> 0; +function md5cycle(x: Uint32Array, k: Uint32Array): void { + let a = x[0]!; + let b = x[1]!; + let c = x[2]!; + let d = x[3]!; + + a = ff(a, b, c, d, k[0]!, 7, -680876936); + d = ff(d, a, b, c, k[1]!, 12, -389564586); + c = ff(c, d, a, b, k[2]!, 17, 606105819); + b = ff(b, c, d, a, k[3]!, 22, -1044525330); + a = ff(a, b, c, d, k[4]!, 7, -176418897); + d = ff(d, a, b, c, k[5]!, 12, 1200080426); + c = ff(c, d, a, b, k[6]!, 17, -1473231341); + b = ff(b, c, d, a, k[7]!, 22, -45705983); + a = ff(a, b, c, d, k[8]!, 7, 1770035416); + d = ff(d, a, b, c, k[9]!, 12, -1958414417); + c = ff(c, d, a, b, k[10]!, 17, -42063); + b = ff(b, c, d, a, k[11]!, 22, -1990404162); + a = ff(a, b, c, d, k[12]!, 7, 1804603682); + d = ff(d, a, b, c, k[13]!, 12, -40341101); + c = ff(c, d, a, b, k[14]!, 17, -1502002290); + b = ff(b, c, d, a, k[15]!, 22, 1236535329); + + a = gg(a, b, c, d, k[1]!, 5, -165796510); + d = gg(d, a, b, c, k[6]!, 9, -1069501632); + c = gg(c, d, a, b, k[11]!, 14, 643717713); + b = gg(b, c, d, a, k[0]!, 20, -373897302); + a = gg(a, b, c, d, k[5]!, 5, -701558691); + d = gg(d, a, b, c, k[10]!, 9, 38016083); + c = gg(c, d, a, b, k[15]!, 14, -660478335); + b = gg(b, c, d, a, k[4]!, 20, -405537848); + a = gg(a, b, c, d, k[9]!, 5, 568446438); + d = gg(d, a, b, c, k[14]!, 9, -1019803690); + c = gg(c, d, a, b, k[3]!, 14, -187363961); + b = gg(b, c, d, a, k[8]!, 20, 1163531501); + a = gg(a, b, c, d, k[13]!, 5, -1444681467); + d = gg(d, a, b, c, k[2]!, 9, -51403784); + c = gg(c, d, a, b, k[7]!, 14, 1735328473); + b = gg(b, c, d, a, k[12]!, 20, -1926607734); + + a = hh(a, b, c, d, k[5]!, 4, -378558); + d = hh(d, a, b, c, k[8]!, 11, -2022574463); + c = hh(c, d, a, b, k[11]!, 16, 1839030562); + b = hh(b, c, d, a, k[14]!, 23, -35309556); + a = hh(a, b, c, d, k[1]!, 4, -1530992060); + d = hh(d, a, b, c, k[4]!, 11, 1272893353); + c = hh(c, d, a, b, k[7]!, 16, -155497632); + b = hh(b, c, d, a, k[10]!, 23, -1094730640); + a = hh(a, b, c, d, k[13]!, 4, 681279174); + d = hh(d, a, b, c, k[0]!, 11, -358537222); + c = hh(c, d, a, b, k[3]!, 16, -722521979); + b = hh(b, c, d, a, k[6]!, 23, 76029189); + a = hh(a, b, c, d, k[9]!, 4, -640364487); + d = hh(d, a, b, c, k[12]!, 11, -421815835); + c = hh(c, d, a, b, k[15]!, 16, 530742520); + b = hh(b, c, d, a, k[2]!, 23, -995338651); + + a = ii(a, b, c, d, k[0]!, 6, -198630844); + d = ii(d, a, b, c, k[7]!, 10, 1126891415); + c = ii(c, d, a, b, k[14]!, 15, -1416354905); + b = ii(b, c, d, a, k[5]!, 21, -57434055); + a = ii(a, b, c, d, k[12]!, 6, 1700485571); + d = ii(d, a, b, c, k[3]!, 10, -1894986606); + c = ii(c, d, a, b, k[10]!, 15, -1051523); + b = ii(b, c, d, a, k[1]!, 21, -2054922799); + a = ii(a, b, c, d, k[8]!, 6, 1873313359); + d = ii(d, a, b, c, k[15]!, 10, -30611744); + c = ii(c, d, a, b, k[6]!, 15, -1560198380); + b = ii(b, c, d, a, k[13]!, 21, 1309151649); + a = ii(a, b, c, d, k[4]!, 6, -145523070); + d = ii(d, a, b, c, k[11]!, 10, -1120210379); + c = ii(c, d, a, b, k[2]!, 15, 718787259); + b = ii(b, c, d, a, k[9]!, 21, -343485551); + + x[0] = (a + x[0]!) >>> 0; + x[1] = (b + x[1]!) >>> 0; + x[2] = (c + x[2]!) >>> 0; + x[3] = (d + x[3]!) >>> 0; } -export function md5(key: ArrayBufferLike) { +export function md5(key: ArrayBufferLike): Uint8Array { const dataView = new DataView(key); - let i; + let i: number; const l = dataView.byteLength; const n = l & ~63; const block = new Uint32Array(16); @@ -111,7 +111,6 @@ export function md5(key: ArrayBufferLike) { for (let w = 0; w < 16; ++w) { block[w] = dataView.getUint32(i + (w << 2), true); } - md5cycle(state, block); } @@ -142,7 +141,6 @@ export function md5(key: ArrayBufferLike) { for (; w < 16; ++w) { block[w] = 0; } - md5cycle(state, block); w = 0; } From 40238200f9482588df3d25ae23122bc69d878c66 Mon Sep 17 00:00:00 2001 From: Jonas Alves Date: Sat, 28 Mar 2026 17:23:18 +0000 Subject: [PATCH 05/22] feat: add hashing utilities (stringToUint8Array, base64UrlNoPadding, hashUnit) --- src/__tests__/hashing.test.ts | 60 +++++++++++++++++++++++++++++++ src/hashing.ts | 67 +++++++++++++++++++++++++++++++++++ 2 files changed, 127 insertions(+) create mode 100644 src/__tests__/hashing.test.ts create mode 100644 src/hashing.ts diff --git a/src/__tests__/hashing.test.ts b/src/__tests__/hashing.test.ts new file mode 100644 index 0000000..f1038cc --- /dev/null +++ b/src/__tests__/hashing.test.ts @@ -0,0 +1,60 @@ +import { describe, expect, test } from "vitest"; +import { base64UrlNoPadding, hashUnit, stringToUint8Array } from "../hashing"; + +describe("stringToUint8Array", () => { + test("encodes ASCII", () => { + const result = stringToUint8Array("abc"); + expect(Array.from(result)).toEqual([97, 98, 99]); + }); + + test("encodes multi-byte characters", () => { + const result = stringToUint8Array("\u00e9"); + expect(Array.from(result)).toEqual([0xc3, 0xa9]); + }); + + test("encodes empty string", () => { + const result = stringToUint8Array(""); + expect(result.length).toBe(0); + }); +}); + +describe("base64UrlNoPadding", () => { + test("encodes empty", () => { + expect(base64UrlNoPadding(new Uint8Array([]))).toBe(""); + }); + + test("encodes 1 byte", () => { + expect(base64UrlNoPadding(new Uint8Array([0]))).toBe("AA"); + }); + + test("encodes 2 bytes", () => { + expect(base64UrlNoPadding(new Uint8Array([0, 0]))).toBe("AAA"); + }); + + test("encodes 3 bytes", () => { + expect(base64UrlNoPadding(new Uint8Array([0, 0, 0]))).toBe("AAAA"); + }); +}); + +describe("hashUnit", () => { + test("hashes string unit", () => { + const result = hashUnit("test_unit"); + expect(typeof result).toBe("string"); + expect(result.length).toBeGreaterThan(0); + }); + + test("hashes numeric unit", () => { + const result = hashUnit(12345); + expect(typeof result).toBe("string"); + expect(result.length).toBeGreaterThan(0); + }); + + test("produces consistent results", () => { + expect(hashUnit("abc")).toBe(hashUnit("abc")); + expect(hashUnit(123)).toBe(hashUnit(123)); + }); + + test("produces different results for different inputs", () => { + expect(hashUnit("abc")).not.toBe(hashUnit("def")); + }); +}); diff --git a/src/hashing.ts b/src/hashing.ts new file mode 100644 index 0000000..0024d81 --- /dev/null +++ b/src/hashing.ts @@ -0,0 +1,67 @@ +import { md5 } from "./md5"; + +export function stringToUint8Array(value: string): Uint8Array { + const n = value.length; + const array: number[] = []; + let k = 0; + for (let i = 0; i < n; ++i) { + const c = value.charCodeAt(i); + if (c < 0x80) { + array[k++] = c; + } else if (c < 0x800) { + array[k++] = (c >> 6) | 192; + array[k++] = (c & 63) | 128; + } else { + array[k++] = (c >> 12) | 224; + array[k++] = ((c >> 6) & 63) | 128; + array[k++] = (c & 63) | 128; + } + } + return Uint8Array.from(array); +} + +const BASE64_URL_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + +export function base64UrlNoPadding(value: Uint8Array): string { + const chars = BASE64_URL_CHARS; + const remaining = value.byteLength % 3; + const encodeLen = ((value.byteLength / 3) | 0) * 4 + (remaining === 0 ? 0 : remaining === 1 ? 2 : 3); + const result = new Array(encodeLen); + + let i: number; + let out = 0; + const len = value.byteLength - remaining; + for (i = 0; i < len; i += 3) { + const bytes = (value[i]! << 16) | (value[i + 1]! << 8) | value[i + 2]!; + result[out] = chars[(bytes >> 18) & 63]!; + result[out + 1] = chars[(bytes >> 12) & 63]!; + result[out + 2] = chars[(bytes >> 6) & 63]!; + result[out + 3] = chars[bytes & 63]!; + out += 4; + } + + switch (remaining) { + case 2: { + const bytes = (value[i]! << 16) | (value[i + 1]! << 8); + result[out] = chars[(bytes >> 18) & 63]!; + result[out + 1] = chars[(bytes >> 12) & 63]!; + result[out + 2] = chars[(bytes >> 6) & 63]!; + break; + } + case 1: { + const bytes = value[i]! << 16; + result[out] = chars[(bytes >> 18) & 63]!; + result[out + 1] = chars[(bytes >> 12) & 63]!; + break; + } + default: + break; + } + + return result.join(""); +} + +export function hashUnit(value: string | number): string { + const unit = typeof value === "string" ? value : value.toFixed(0); + return base64UrlNoPadding(md5(stringToUint8Array(unit).buffer)); +} From 6651b79e23037041277c681bc61e4f28347c4dd8 Mon Sep 17 00:00:00 2001 From: Jonas Alves Date: Sat, 28 Mar 2026 17:23:18 +0000 Subject: [PATCH 06/22] feat: add core utilities (isObject, isEqualsDeep, chooseVariant, insertUniqueSorted) --- src/__tests__/algorithm.test.ts | 34 +++++++ src/__tests__/utils.test.ts | 113 +++++++++++++++++++++++ src/algorithm.ts | 13 ++- src/utils.ts | 159 +++++--------------------------- 4 files changed, 178 insertions(+), 141 deletions(-) create mode 100644 src/__tests__/algorithm.test.ts create mode 100644 src/__tests__/utils.test.ts diff --git a/src/__tests__/algorithm.test.ts b/src/__tests__/algorithm.test.ts new file mode 100644 index 0000000..9fce62c --- /dev/null +++ b/src/__tests__/algorithm.test.ts @@ -0,0 +1,34 @@ +import { describe, expect, test } from "vitest"; +import { insertUniqueSorted } from "../algorithm"; + +describe("insertUniqueSorted", () => { + test("inserts into empty array", () => { + const arr: number[] = []; + insertUniqueSorted(arr, 5, (a, b) => a < b); + expect(arr).toEqual([5]); + }); + + test("inserts in sorted order", () => { + const arr = [1, 3, 5]; + insertUniqueSorted(arr, 2, (a, b) => a < b); + expect(arr).toEqual([1, 2, 3, 5]); + }); + + test("inserts at beginning", () => { + const arr = [2, 3, 4]; + insertUniqueSorted(arr, 1, (a, b) => a < b); + expect(arr).toEqual([1, 2, 3, 4]); + }); + + test("inserts at end", () => { + const arr = [1, 2, 3]; + insertUniqueSorted(arr, 4, (a, b) => a < b); + expect(arr).toEqual([1, 2, 3, 4]); + }); + + test("does not insert duplicate", () => { + const arr = [1, 2, 3]; + insertUniqueSorted(arr, 2, (a, b) => a < b); + expect(arr).toEqual([1, 2, 3]); + }); +}); diff --git a/src/__tests__/utils.test.ts b/src/__tests__/utils.test.ts new file mode 100644 index 0000000..c98b29c --- /dev/null +++ b/src/__tests__/utils.test.ts @@ -0,0 +1,113 @@ +import { describe, expect, test } from "vitest"; +import { arrayEqualsShallow, chooseVariant, isEqualsDeep, isObject, isPromise } from "../utils"; + +describe("isObject", () => { + test("returns true for plain objects", () => { + expect(isObject({})).toBe(true); + expect(isObject({ a: 1 })).toBe(true); + }); + + test("returns false for non-objects", () => { + expect(isObject(null)).toBe(false); + expect(isObject(undefined)).toBe(false); + expect(isObject(42)).toBe(false); + expect(isObject("str")).toBe(false); + expect(isObject([])).toBe(false); + expect(isObject(new Date())).toBe(false); + }); +}); + +describe("isPromise", () => { + test("returns true for promises", () => { + expect(isPromise(Promise.resolve())).toBe(true); + expect(isPromise({ then: () => {} })).toBe(true); + }); + + test("returns false for non-promises", () => { + expect(isPromise(null)).toBe(false); + expect(isPromise(undefined)).toBe(false); + expect(isPromise({})).toBe(false); + expect(isPromise(42)).toBe(false); + }); +}); + +describe("isEqualsDeep", () => { + test("primitives", () => { + expect(isEqualsDeep(1, 1)).toBe(true); + expect(isEqualsDeep(1, 2)).toBe(false); + expect(isEqualsDeep("a", "a")).toBe(true); + expect(isEqualsDeep("a", "b")).toBe(false); + expect(isEqualsDeep(true, true)).toBe(true); + expect(isEqualsDeep(true, false)).toBe(false); + }); + + test("NaN", () => { + expect(isEqualsDeep(NaN, NaN)).toBe(true); + }); + + test("arrays", () => { + expect(isEqualsDeep([1, 2, 3], [1, 2, 3])).toBe(true); + expect(isEqualsDeep([1, 2, 3], [1, 2, 4])).toBe(false); + expect(isEqualsDeep([1, 2], [1, 2, 3])).toBe(false); + }); + + test("objects", () => { + expect(isEqualsDeep({ a: 1, b: 2 }, { a: 1, b: 2 })).toBe(true); + expect(isEqualsDeep({ a: 1 }, { a: 2 })).toBe(false); + expect(isEqualsDeep({ a: 1 }, { a: 1, b: 2 })).toBe(false); + }); + + test("nested structures", () => { + expect(isEqualsDeep({ a: [1, { b: 2 }] }, { a: [1, { b: 2 }] })).toBe(true); + expect(isEqualsDeep({ a: [1, { b: 2 }] }, { a: [1, { b: 3 }] })).toBe(false); + }); + + test("different types", () => { + expect(isEqualsDeep(1, "1")).toBe(false); + expect(isEqualsDeep([], {})).toBe(false); + }); +}); + +describe("arrayEqualsShallow", () => { + test("same reference", () => { + const arr = [1, 2, 3]; + expect(arrayEqualsShallow(arr, arr)).toBe(true); + }); + + test("equal arrays", () => { + expect(arrayEqualsShallow([1, 2, 3], [1, 2, 3])).toBe(true); + }); + + test("different arrays", () => { + expect(arrayEqualsShallow([1, 2, 3], [1, 2, 4])).toBe(false); + }); + + test("different lengths", () => { + expect(arrayEqualsShallow([1, 2], [1, 2, 3])).toBe(false); + }); + + test("both undefined", () => { + expect(arrayEqualsShallow(undefined, undefined)).toBe(true); + }); +}); + +describe("chooseVariant", () => { + test("selects correct variant based on probability", () => { + expect(chooseVariant([0.5, 0.5], 0.0)).toBe(0); + expect(chooseVariant([0.5, 0.5], 0.4)).toBe(0); + expect(chooseVariant([0.5, 0.5], 0.5)).toBe(1); + expect(chooseVariant([0.5, 0.5], 0.9)).toBe(1); + }); + + test("three-way split", () => { + expect(chooseVariant([0.33, 0.33, 0.34], 0.0)).toBe(0); + expect(chooseVariant([0.33, 0.33, 0.34], 0.3)).toBe(0); + expect(chooseVariant([0.33, 0.33, 0.34], 0.33)).toBe(1); + expect(chooseVariant([0.33, 0.33, 0.34], 0.65)).toBe(1); + expect(chooseVariant([0.33, 0.33, 0.34], 0.66)).toBe(2); + }); + + test("returns last variant for probability >= 1", () => { + expect(chooseVariant([0.5, 0.5], 1.0)).toBe(1); + }); +}); diff --git a/src/algorithm.ts b/src/algorithm.ts index d4c4857..bfb83fb 100644 --- a/src/algorithm.ts +++ b/src/algorithm.ts @@ -1,13 +1,16 @@ -export const insertUniqueSorted = (arr: TData[], value: TData, isSorted: (a: TData, b: TData) => boolean) => { +export function insertUniqueSorted( + arr: TData[], + value: TData, + isSorted: (a: TData, b: TData) => boolean, +): void { let left = 0; let right = arr.length - 1; while (left <= right) { const mid = Math.floor(left + (right - left) / 2); - - if (isSorted(arr[mid], value)) { + if (isSorted(arr[mid]!, value)) { left = mid + 1; - } else if (isSorted(value, arr[mid])) { + } else if (isSorted(value, arr[mid]!)) { right = mid - 1; } else { return; @@ -15,4 +18,4 @@ export const insertUniqueSorted = (arr: TData[], value: TData, isSorted: } arr.splice(left, 0, value); -}; +} diff --git a/src/utils.ts b/src/utils.ts index 529d486..485aea3 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,36 +1,5 @@ -import { md5 } from "./md5"; - -export const getApplicationName = (app: string | { name: string; version: number | string }): string => - typeof app !== "string" ? app.name : app; - -export const getApplicationVersion = (app: string | { name: string; version: number | string }): number | string => - typeof app !== "string" ? app.version : 0; - -function isBrowser() { - return typeof window !== "undefined" && typeof window.document !== "undefined"; -} - -function isReactNative() { - return typeof navigator !== "undefined" && navigator.product === "ReactNative"; -} - -export function isLongLivedApp() { - return isBrowser() || isReactNative(); -} - -export function isWorker() { - return typeof self === "object" && self.constructor && self.constructor.name === "DedicatedWorkerGlobalScope"; -} - -export function isNumeric(value: unknown): value is number { - return typeof value === "number"; -} - export function isObject(value: unknown): value is Record { - if (!(value instanceof Object)) { - return false; - } - + if (!(value instanceof Object)) return false; const proto = Object.getPrototypeOf(value); return proto == null || proto === Object.prototype; } @@ -39,21 +8,17 @@ export function isPromise(value: unknown): value is Promise { return value !== null && typeof value === "object" && typeof (value as Promise).then === "function"; } -function arrayEqualsDeep(a: unknown[], b: unknown[], astack: unknown[] = [], bstack: unknown[] = []) { - let len = astack?.length ?? 0; +function arrayEqualsDeep(a: unknown[], b: unknown[], astack: unknown[] = [], bstack: unknown[] = []): boolean { + let len = astack.length; while (len--) { if (astack[len] === a) return bstack[len] === b; } - astack = astack ?? []; - bstack = bstack ?? []; - astack.push(a); bstack.push(b); len = a.length; while (len--) { - // eslint-disable-next-line no-use-before-define if (!isEqualsDeep(a[len], b[len], astack, bstack)) return false; } @@ -68,11 +33,11 @@ function objectEqualsDeep( b: Record, keys: string[], astack?: unknown[], - bstack?: unknown[] -) { + bstack?: unknown[], +): boolean { let len = astack?.length ?? 0; while (len--) { - if (astack && astack[len] === a) return bstack && bstack[len] === b; + if (astack && astack[len] === a) return bstack !== undefined && bstack[len] === b; } astack = astack ?? []; @@ -83,11 +48,8 @@ function objectEqualsDeep( len = keys.length; while (len--) { - const key = keys[len]; - // eslint-disable-next-line no-prototype-builtins + const key = keys[len]!; if (!Object.prototype.hasOwnProperty.call(b, key)) return false; - - // eslint-disable-next-line no-use-before-define if (!isEqualsDeep(a[key], b[key], astack, bstack)) return false; } @@ -97,7 +59,7 @@ function objectEqualsDeep( return true; } -export function isEqualsDeep(a: unknown, b: unknown, astack?: unknown[], bstack?: unknown[]) { +export function isEqualsDeep(a: unknown, b: unknown, astack?: unknown[], bstack?: unknown[]): boolean { if (a === b) return true; if (typeof a !== typeof b) return false; @@ -105,7 +67,7 @@ export function isEqualsDeep(a: unknown, b: unknown, astack?: unknown[], bstack? case "boolean": return a === b; case "number": - if (Number.isNaN(a)) return Number.isNaN(b); + if (Number.isNaN(a)) return Number.isNaN(b as number); return a === b; case "string": return a === b; @@ -122,18 +84,16 @@ export function isEqualsDeep(a: unknown, b: unknown, astack?: unknown[], bstack? if (a.length === b.length) { return arrayEqualsDeep(a, b, astack, bstack); } - } else { - if (a && b) { - const keys = Object.keys(a); - if (keys.length === Object.keys(b).length) { - return objectEqualsDeep( - a as Record, - b as Record, - keys, - astack, - bstack - ); - } + } else if (a && b) { + const keys = Object.keys(a); + if (keys.length === Object.keys(b as Record).length) { + return objectEqualsDeep( + a as Record, + b as Record, + keys, + astack, + bstack, + ); } } break; @@ -144,88 +104,15 @@ export function isEqualsDeep(a: unknown, b: unknown, astack?: unknown[], bstack? return false; } -export function arrayEqualsShallow(a?: unknown[], b?: unknown[]) { +export function arrayEqualsShallow(a?: unknown[], b?: unknown[]): boolean { return a === b || (a?.length === b?.length && !a?.some((va, vi) => b && va !== b[vi])); } -export function stringToUint8Array(value: string) { - const n = value.length; - const array = new Array(value.length); - - let k = 0; - for (let i = 0; i < n; ++i) { - const c = value.charCodeAt(i); - if (c < 0x80) { - array[k++] = c; - } else if (c < 0x800) { - array[k++] = (c >> 6) | 192; - array[k++] = (c & 63) | 128; - } else { - array[k++] = (c >> 12) | 224; - array[k++] = ((c >> 6) & 63) | 128; - array[k++] = (c & 63) | 128; - } - } - return Uint8Array.from(array); -} - -const Base64URLNoPaddingChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; - -export function base64UrlNoPadding(value: Uint8Array) { - const chars = Base64URLNoPaddingChars; - - const remaining = value.byteLength % 3; - const encodeLen = ((value.byteLength / 3) | 0) * 4 + (remaining === 0 ? 0 : remaining === 1 ? 2 : 3); - const result = new Array(encodeLen); - - let i; - let out = 0; - const len = value.byteLength - remaining; - for (i = 0; i < len; i += 3) { - const bytes = (value[i] << 16) | (value[i + 1] << 8) | value[i + 2]; - result[out] = chars[(bytes >> 18) & 63]; - result[out + 1] = chars[(bytes >> 12) & 63]; - result[out + 2] = chars[(bytes >> 6) & 63]; - result[out + 3] = chars[bytes & 63]; - out += 4; - } - - switch (remaining) { - case 2: - { - const bytes = (value[i] << 16) | (value[i + 1] << 8); - result[out] = chars[(bytes >> 18) & 63]; - result[out + 1] = chars[(bytes >> 12) & 63]; - result[out + 2] = chars[(bytes >> 6) & 63]; - } - break; - case 1: - { - const bytes = value[i] << 16; - result[out] = chars[(bytes >> 18) & 63]; - result[out + 1] = chars[(bytes >> 12) & 63]; - } - break; - default: - break; - } - - return result.join(""); -} - -export function hashUnit(value: string | number) { - const unit = typeof value === "string" ? value : value.toFixed(0); - return base64UrlNoPadding(md5(stringToUint8Array(unit).buffer)); -} - -export function chooseVariant(split: number[], prob: number) { +export function chooseVariant(split: number[], prob: number): number { let cumSum = 0.0; for (let i = 0; i < split.length; ++i) { - cumSum += split[i]; - if (prob < cumSum) { - return i; - } + cumSum += split[i]!; + if (prob < cumSum) return i; } - return split.length - 1; } From 0307d11fbc2a0e51da8125388a90b9abdffcc08b Mon Sep 17 00:00:00 2001 From: Jonas Alves Date: Sat, 28 Mar 2026 23:24:30 +0000 Subject: [PATCH 07/22] feat: add VariantAssigner with murmur3-based probability --- src/__tests__/assigner.test.ts | 67 ++++++++++++++++++++++++++++++++++ src/assigner.ts | 11 +++--- 2 files changed, 73 insertions(+), 5 deletions(-) create mode 100644 src/__tests__/assigner.test.ts diff --git a/src/__tests__/assigner.test.ts b/src/__tests__/assigner.test.ts new file mode 100644 index 0000000..c71d135 --- /dev/null +++ b/src/__tests__/assigner.test.ts @@ -0,0 +1,67 @@ +import { describe, expect, test } from "vitest"; +import { VariantAssigner } from "../assigner"; +import { hashUnit } from "../hashing"; + +describe("VariantAssigner", () => { + const testCases: Record = { + "bleh@absmartly.com": [ + [[0.5, 0.5], 0x00000000, 0x00000000, 0], + [[0.5, 0.5], 0x00000000, 0x00000001, 1], + [[0.5, 0.5], 0x8015406f, 0x7ef49b98, 0], + [[0.5, 0.5], 0x3b2e7d90, 0xca87df4d, 0], + [[0.5, 0.5], 0x52c1f657, 0xd248bb2e, 0], + [[0.5, 0.5], 0x865a84d0, 0xaa22d41a, 0], + [[0.5, 0.5], 0x27d1dc86, 0x845461b9, 1], + [[0.33, 0.33, 0.34], 0x00000000, 0x00000000, 0], + [[0.33, 0.33, 0.34], 0x00000000, 0x00000001, 2], + [[0.33, 0.33, 0.34], 0x8015406f, 0x7ef49b98, 0], + [[0.33, 0.33, 0.34], 0x3b2e7d90, 0xca87df4d, 0], + [[0.33, 0.33, 0.34], 0x52c1f657, 0xd248bb2e, 0], + [[0.33, 0.33, 0.34], 0x865a84d0, 0xaa22d41a, 1], + [[0.33, 0.33, 0.34], 0x27d1dc86, 0x845461b9, 1], + ], + "123456789": [ + [[0.5, 0.5], 0x00000000, 0x00000000, 1], + [[0.5, 0.5], 0x00000000, 0x00000001, 0], + [[0.5, 0.5], 0x8015406f, 0x7ef49b98, 1], + [[0.5, 0.5], 0x3b2e7d90, 0xca87df4d, 1], + [[0.5, 0.5], 0x52c1f657, 0xd248bb2e, 1], + [[0.5, 0.5], 0x865a84d0, 0xaa22d41a, 0], + [[0.5, 0.5], 0x27d1dc86, 0x845461b9, 0], + [[0.33, 0.33, 0.34], 0x00000000, 0x00000000, 2], + [[0.33, 0.33, 0.34], 0x00000000, 0x00000001, 1], + [[0.33, 0.33, 0.34], 0x8015406f, 0x7ef49b98, 2], + [[0.33, 0.33, 0.34], 0x3b2e7d90, 0xca87df4d, 2], + [[0.33, 0.33, 0.34], 0x52c1f657, 0xd248bb2e, 2], + [[0.33, 0.33, 0.34], 0x865a84d0, 0xaa22d41a, 0], + [[0.33, 0.33, 0.34], 0x27d1dc86, 0x845461b9, 0], + ], + e791e240fcd3df7d238cfc285f475e8152fcc0ec: [ + [[0.5, 0.5], 0x00000000, 0x00000000, 1], + [[0.5, 0.5], 0x00000000, 0x00000001, 0], + [[0.5, 0.5], 0x8015406f, 0x7ef49b98, 1], + [[0.5, 0.5], 0x3b2e7d90, 0xca87df4d, 1], + [[0.5, 0.5], 0x52c1f657, 0xd248bb2e, 0], + [[0.5, 0.5], 0x865a84d0, 0xaa22d41a, 0], + [[0.5, 0.5], 0x27d1dc86, 0x845461b9, 0], + [[0.33, 0.33, 0.34], 0x00000000, 0x00000000, 2], + [[0.33, 0.33, 0.34], 0x00000000, 0x00000001, 0], + [[0.33, 0.33, 0.34], 0x8015406f, 0x7ef49b98, 2], + [[0.33, 0.33, 0.34], 0x3b2e7d90, 0xca87df4d, 1], + [[0.33, 0.33, 0.34], 0x52c1f657, 0xd248bb2e, 0], + [[0.33, 0.33, 0.34], 0x865a84d0, 0xaa22d41a, 0], + [[0.33, 0.33, 0.34], 0x27d1dc86, 0x845461b9, 1], + ], + }; + + for (const [unit, cases] of Object.entries(testCases)) { + describe(`unit: "${unit}"`, () => { + const assigner = new VariantAssigner(hashUnit(unit)); + for (const [split, seedHi, seedLo, expected] of cases) { + test(`assign([${split}], 0x${seedHi.toString(16)}, 0x${seedLo.toString(16)}) == ${expected}`, () => { + expect(assigner.assign(split, seedHi, seedLo)).toBe(expected); + }); + } + }); + } +}); diff --git a/src/assigner.ts b/src/assigner.ts index e744af0..f1b8ec0 100644 --- a/src/assigner.ts +++ b/src/assigner.ts @@ -1,25 +1,26 @@ -import { chooseVariant, stringToUint8Array } from "./utils"; -import { murmur3_32 } from "./murmur3_32"; +import { chooseVariant } from "./utils"; +import { stringToUint8Array } from "./hashing"; +import { murmur3_32 } from "./murmur3"; export class VariantAssigner { private readonly _unitHash: number; + constructor(unit: string) { this._unitHash = murmur3_32(stringToUint8Array(unit).buffer); } - assign(split: number[], seedHi: number, seedLo: number) { + assign(split: number[], seedHi: number, seedLo: number): number { const prob = this._probability(seedHi, seedLo); return chooseVariant(split, prob); } - private _probability(seedHi: number, seedLo: number) { + private _probability(seedHi: number, seedLo: number): number { const key = this._unitHash; const buffer = new ArrayBuffer(12); const view = new DataView(buffer); view.setUint32(0, seedLo, true); view.setUint32(4, seedHi, true); view.setUint32(8, key, true); - return murmur3_32(buffer) * (1.0 / 0xffffffff); } } From d47db227060b520dd8b60369aa145fe5cd230b8d Mon Sep 17 00:00:00 2001 From: Jonas Alves Date: Sat, 28 Mar 2026 23:24:30 +0000 Subject: [PATCH 08/22] feat: add JsonExpr evaluator with type conversion and comparison --- src/__tests__/jsonexpr/evaluator.test.ts | 167 +++++++++++++++++++++++ src/jsonexpr/evaluator.ts | 97 +++++-------- 2 files changed, 202 insertions(+), 62 deletions(-) create mode 100644 src/__tests__/jsonexpr/evaluator.test.ts diff --git a/src/__tests__/jsonexpr/evaluator.test.ts b/src/__tests__/jsonexpr/evaluator.test.ts new file mode 100644 index 0000000..0122340 --- /dev/null +++ b/src/__tests__/jsonexpr/evaluator.test.ts @@ -0,0 +1,167 @@ +import { describe, expect, test } from "vitest"; +import { Evaluator } from "../../jsonexpr/evaluator"; + +function createEvaluator(vars: Record = {}) { + return new Evaluator({}, vars); +} + +describe("Evaluator", () => { + describe("booleanConvert", () => { + const evaluator = createEvaluator(); + + test("boolean values", () => { + expect(evaluator.booleanConvert(true)).toBe(true); + expect(evaluator.booleanConvert(false)).toBe(false); + }); + + test("number values", () => { + expect(evaluator.booleanConvert(1)).toBe(true); + expect(evaluator.booleanConvert(0)).toBe(false); + expect(evaluator.booleanConvert(-1)).toBe(true); + }); + + test("string values", () => { + expect(evaluator.booleanConvert("true")).toBe(true); + expect(evaluator.booleanConvert("false")).toBe(false); + expect(evaluator.booleanConvert("0")).toBe(false); + expect(evaluator.booleanConvert("")).toBe(false); + expect(evaluator.booleanConvert("abc")).toBe(true); + }); + + test("null/undefined", () => { + expect(evaluator.booleanConvert(null)).toBe(false); + expect(evaluator.booleanConvert(undefined)).toBe(false); + }); + }); + + describe("numberConvert", () => { + const evaluator = createEvaluator(); + + test("number values", () => { + expect(evaluator.numberConvert(42)).toBe(42); + expect(evaluator.numberConvert(0)).toBe(0); + expect(evaluator.numberConvert(-1.5)).toBe(-1.5); + }); + + test("boolean values", () => { + expect(evaluator.numberConvert(true)).toBe(1); + expect(evaluator.numberConvert(false)).toBe(0); + }); + + test("string values", () => { + expect(evaluator.numberConvert("42")).toBe(42); + expect(evaluator.numberConvert("3.14")).toBe(3.14); + expect(evaluator.numberConvert("abc")).toBe(null); + }); + + test("other types", () => { + expect(evaluator.numberConvert(null)).toBe(null); + expect(evaluator.numberConvert({})).toBe(null); + }); + }); + + describe("stringConvert", () => { + const evaluator = createEvaluator(); + + test("string values", () => { + expect(evaluator.stringConvert("hello")).toBe("hello"); + }); + + test("boolean values", () => { + expect(evaluator.stringConvert(true)).toBe("true"); + expect(evaluator.stringConvert(false)).toBe("false"); + }); + + test("number values", () => { + expect(evaluator.stringConvert(42)).toBe("42"); + expect(evaluator.stringConvert(0)).toBe("0"); + }); + + test("other types", () => { + expect(evaluator.stringConvert(null)).toBe(null); + expect(evaluator.stringConvert({})).toBe(null); + }); + }); + + describe("extractVar", () => { + test("extracts top-level variable", () => { + const evaluator = createEvaluator({ name: "John" }); + expect(evaluator.extractVar("name")).toBe("John"); + }); + + test("extracts nested variable", () => { + const evaluator = createEvaluator({ user: { name: "John" } }); + expect(evaluator.extractVar("user/name")).toBe("John"); + }); + + test("returns null for missing path", () => { + const evaluator = createEvaluator({ name: "John" }); + expect(evaluator.extractVar("missing")).toBe(null); + }); + }); + + describe("compare", () => { + const evaluator = createEvaluator(); + + test("numbers", () => { + expect(evaluator.compare(1, 2)).toBe(-1); + expect(evaluator.compare(2, 1)).toBe(1); + expect(evaluator.compare(1, 1)).toBe(0); + }); + + test("strings", () => { + expect(evaluator.compare("a", "b")).toBe(-1); + expect(evaluator.compare("b", "a")).toBe(1); + expect(evaluator.compare("a", "a")).toBe(0); + }); + + test("booleans", () => { + expect(evaluator.compare(true, true)).toBe(0); + expect(evaluator.compare(false, false)).toBe(0); + }); + + test("null handling", () => { + expect(evaluator.compare(null, null)).toBe(0); + expect(evaluator.compare(null, 1)).toBe(null); + expect(evaluator.compare(1, null)).toBe(null); + }); + }); + + describe("versionCompare", () => { + const evaluator = createEvaluator(); + + test("equal versions", () => { + expect(evaluator.versionCompare("1.0.0", "1.0.0")).toBe(0); + }); + + test("greater version", () => { + expect(evaluator.versionCompare("2.0.0", "1.0.0")).toBe(1); + }); + + test("lesser version", () => { + expect(evaluator.versionCompare("1.0.0", "2.0.0")).toBe(-1); + }); + + test("prerelease is less than release", () => { + expect(evaluator.versionCompare("1.0.0-alpha", "1.0.0")).toBe(-1); + }); + + test("v prefix", () => { + expect(evaluator.versionCompare("v1.0.0", "1.0.0")).toBe(0); + }); + + test("build metadata ignored", () => { + expect(evaluator.versionCompare("1.0.0+build1", "1.0.0+build2")).toBe(0); + }); + + test("null inputs", () => { + expect(evaluator.versionCompare(null, "1.0.0")).toBe(null); + expect(evaluator.versionCompare("1.0.0", null)).toBe(null); + }); + + test("empty inputs", () => { + expect(evaluator.versionCompare("", "1.0.0")).toBe(null); + expect(evaluator.versionCompare("1.0.0", "")).toBe(null); + }); + }); +}); diff --git a/src/jsonexpr/evaluator.ts b/src/jsonexpr/evaluator.ts index 5c52ff0..905e6f8 100644 --- a/src/jsonexpr/evaluator.ts +++ b/src/jsonexpr/evaluator.ts @@ -1,6 +1,9 @@ -/* eslint-disable */ import { isEqualsDeep, isObject } from "../utils"; +export interface Operator { + evaluate(evaluator: Evaluator, args: unknown): unknown; +} + function parseSemver(version: string) { let v = version; if (v.startsWith("v") || v.startsWith("V")) { @@ -12,39 +15,32 @@ function parseSemver(version: string) { v = v.substring(0, plusIndex); } - if (v === "") { - return null; - } + if (v === "") return null; const [core, ...preReleaseParts] = v.split("-"); const preRelease = preReleaseParts.join("-"); - if (core === "") { - return null; - } - - const parts = core.split("."); + if (core === "") return null; + const parts = core!.split("."); return { parts, preRelease }; } const NUMERIC_IDENTIFIER = /^\d+$/; -function stripLeadingZeros(s: string) { +function stripLeadingZeros(s: string): string { const stripped = s.replace(/^0+/, ""); return stripped === "" ? "0" : stripped; } -function compareIdentifiers(a: string, b: string) { +function compareIdentifiers(a: string, b: string): number { const aIsNum = NUMERIC_IDENTIFIER.test(a); const bIsNum = NUMERIC_IDENTIFIER.test(b); if (aIsNum && bIsNum) { const aNorm = stripLeadingZeros(a); const bNorm = stripLeadingZeros(b); - if (aNorm.length !== bNorm.length) { - return aNorm.length > bNorm.length ? 1 : -1; - } + if (aNorm.length !== bNorm.length) return aNorm.length > bNorm.length ? 1 : -1; return aNorm === bNorm ? 0 : aNorm > bNorm ? 1 : -1; } if (aIsNum) return -1; @@ -53,17 +49,17 @@ function compareIdentifiers(a: string, b: string) { } export class Evaluator { - private readonly operators: any; - private readonly vars: any; + private readonly operators: Record; + private readonly vars: Record; - constructor(operators: any, vars: any) { + constructor(operators: Record, vars: Record) { this.operators = operators; this.vars = vars; } - evaluate(expr: TExpr) { + evaluate(expr: unknown): unknown { if (Array.isArray(expr)) { - return this.operators["and"].evaluate(this, expr); + return this.operators["and"]?.evaluate(this, expr) ?? null; } else if (isObject(expr)) { for (const [key, value] of Object.entries(expr)) { const op = this.operators[key]; @@ -73,13 +69,11 @@ export class Evaluator { break; } } - return null; } - booleanConvert(x: TData) { - const type = typeof x; - switch (type) { + booleanConvert(x: unknown): boolean { + switch (typeof x) { case "boolean": return x; case "number": @@ -91,7 +85,7 @@ export class Evaluator { } } - numberConvert(x: TData) { + numberConvert(x: unknown): number | null { switch (typeof x) { case "number": return x; @@ -106,7 +100,7 @@ export class Evaluator { } } - stringConvert(x: TData) { + stringConvert(x: unknown): string | null { switch (typeof x) { case "string": return x; @@ -119,37 +113,27 @@ export class Evaluator { } } - extractVar(path: string) { + extractVar(path: string): unknown { const frags = path.split("/"); - - let target = this.vars ?? {}; - for (let index = 0; index < frags.length; ++index) { - const frag = frags[index]; - - const value = target[frag]; - if (value !== undefined) { - target = value; + let target: unknown = this.vars ?? {}; + for (const frag of frags) { + if (target !== null && typeof target === "object" && frag in (target as Record)) { + target = (target as Record)[frag]; continue; } - return null; } - return target; } - versionCompare(lhs: TData, rhs: TData): number | null { + versionCompare(lhs: unknown, rhs: unknown): number | null { const lhsStr = this.stringConvert(lhs); const rhsStr = this.stringConvert(rhs); - if (lhsStr === null || rhsStr === null || lhsStr === "" || rhsStr === "") { - return null; - } + if (lhsStr === null || rhsStr === null || lhsStr === "" || rhsStr === "") return null; const l = parseSemver(lhsStr); const r = parseSemver(rhsStr); - if (l === null || r === null) { - return null; - } + if (l === null || r === null) return null; const maxLen = Math.max(l.parts.length, r.parts.length); for (let i = 0; i < maxLen; i++) { @@ -169,46 +153,35 @@ export class Evaluator { for (let i = 0; i < preLen; i++) { if (i >= lPreParts.length) return -1; if (i >= rPreParts.length) return 1; - const result = compareIdentifiers(lPreParts[i], rPreParts[i]); + const result = compareIdentifiers(lPreParts[i]!, rPreParts[i]!); if (result !== 0) return result; } return 0; } - compare(lhs: TData, rhs: TData) { - if (lhs === null) { - return rhs === null ? 0 : null; - } else if (rhs === null) { - return null; - } + compare(lhs: unknown, rhs: unknown): number | null { + if (lhs === null) return rhs === null ? 0 : null; + if (rhs === null) return null; switch (typeof lhs) { case "number": { const rvalue = this.numberConvert(rhs); - if (rvalue !== null) { - return lhs === rvalue ? 0 : lhs > rvalue ? 1 : -1; - } + if (rvalue !== null) return lhs === rvalue ? 0 : lhs > rvalue ? 1 : -1; break; } case "string": { const rvalue = this.stringConvert(rhs); - if (rvalue !== null) { - return lhs === rvalue ? 0 : lhs > rvalue ? 1 : -1; - } + if (rvalue !== null) return lhs === rvalue ? 0 : lhs > rvalue ? 1 : -1; break; } case "boolean": { const rvalue = this.booleanConvert(rhs); - if (rvalue != null) { - return lhs === rvalue ? 0 : lhs > rvalue ? 1 : -1; - } + if (rvalue != null) return lhs === rvalue ? 0 : lhs > rvalue ? 1 : -1; break; } default: { - if (isEqualsDeep(lhs, rhs)) { - return 0; - } + if (isEqualsDeep(lhs, rhs)) return 0; break; } } From 879b2611080dfa134bb5c9fa6d0a93a35783d456 Mon Sep 17 00:00:00 2001 From: Jonas Alves Date: Sat, 28 Mar 2026 23:24:30 +0000 Subject: [PATCH 09/22] feat: add all 20 JsonExpr operators --- src/jsonexpr/operators.ts | 182 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 src/jsonexpr/operators.ts diff --git a/src/jsonexpr/operators.ts b/src/jsonexpr/operators.ts new file mode 100644 index 0000000..f380f57 --- /dev/null +++ b/src/jsonexpr/operators.ts @@ -0,0 +1,182 @@ +import { Evaluator } from "./evaluator"; +import { isObject } from "../utils"; + +export class ValueOperator { + evaluate(_: Evaluator, value: unknown): unknown { + return value; + } +} + +export class VarOperator { + evaluate(evaluator: Evaluator, path: unknown): unknown { + if (isObject(path)) { + path = (path as { path: string }).path; + } + return typeof path === "string" ? evaluator.extractVar(path) : null; + } +} + +export class AndCombinator { + evaluate(evaluator: Evaluator, args: unknown): boolean | null { + if (Array.isArray(args)) { + for (const expr of args) { + if (!evaluator.booleanConvert(evaluator.evaluate(expr))) return false; + } + return true; + } + return null; + } +} + +export class OrCombinator { + evaluate(evaluator: Evaluator, args: unknown): boolean | null { + if (Array.isArray(args)) { + for (const expr of args) { + if (evaluator.booleanConvert(evaluator.evaluate(expr))) return true; + } + return args.length === 0; + } + return null; + } +} + +abstract class UnaryOperator { + abstract unary(evaluator: Evaluator, arg: unknown): boolean; + evaluate(evaluator: Evaluator, arg: unknown): boolean { + arg = evaluator.evaluate(arg); + return this.unary(evaluator, arg); + } +} + +export class NotOperator extends UnaryOperator { + unary(evaluator: Evaluator, arg: unknown): boolean { + return !evaluator.booleanConvert(arg); + } +} + +export class NullOperator extends UnaryOperator { + unary(_: Evaluator, value: unknown): boolean { + return value === null; + } +} + +abstract class BinaryOperator { + abstract binary(evaluator: Evaluator, lhs: unknown, rhs: unknown): boolean | null; + evaluate(evaluator: Evaluator, args: unknown): boolean | null { + if (Array.isArray(args)) { + const lhs = args.length > 0 ? evaluator.evaluate(args[0]) : null; + if (lhs !== null) { + const rhs = args.length > 1 ? evaluator.evaluate(args[1]) : null; + if (rhs !== null) { + return this.binary(evaluator, lhs, rhs); + } + } + } + return null; + } +} + +export class EqualsOperator extends BinaryOperator { + binary(evaluator: Evaluator, lhs: unknown, rhs: unknown): boolean | null { + const result = evaluator.compare(lhs, rhs); + return result !== null ? result === 0 : null; + } +} + +export class GreaterThanOperator extends BinaryOperator { + binary(evaluator: Evaluator, lhs: unknown, rhs: unknown): boolean | null { + const result = evaluator.compare(lhs, rhs); + return result !== null ? result > 0 : null; + } +} + +export class GreaterThanOrEqualOperator extends BinaryOperator { + binary(evaluator: Evaluator, lhs: unknown, rhs: unknown): boolean | null { + const result = evaluator.compare(lhs, rhs); + return result !== null ? result >= 0 : null; + } +} + +export class LessThanOperator extends BinaryOperator { + binary(evaluator: Evaluator, lhs: unknown, rhs: unknown): boolean | null { + const result = evaluator.compare(lhs, rhs); + return result !== null ? result < 0 : null; + } +} + +export class LessThanOrEqualOperator extends BinaryOperator { + binary(evaluator: Evaluator, lhs: unknown, rhs: unknown): boolean | null { + const result = evaluator.compare(lhs, rhs); + return result !== null ? result <= 0 : null; + } +} + +export class InOperator extends BinaryOperator { + binary(evaluator: Evaluator, haystack: unknown, needle: unknown): boolean | null { + if (Array.isArray(haystack)) { + for (const item of haystack) { + if (evaluator.compare(item, needle) === 0) return true; + } + return false; + } else if (typeof haystack === "string") { + const needleString = evaluator.stringConvert(needle); + return needleString !== null && haystack.includes(needleString); + } else if (isObject(haystack)) { + const needleString = evaluator.stringConvert(needle); + return needleString != null && Object.prototype.hasOwnProperty.call(haystack, needleString); + } + return null; + } +} + +export class MatchOperator extends BinaryOperator { + binary(evaluator: Evaluator, text: unknown, pattern: unknown): boolean | null { + const textStr = evaluator.stringConvert(text); + if (textStr !== null) { + const patternStr = evaluator.stringConvert(pattern); + if (patternStr !== null) { + try { + return new RegExp(patternStr).test(textStr); + } catch { + return null; + } + } + } + return null; + } +} + +export class SemverEqualsOperator extends BinaryOperator { + binary(evaluator: Evaluator, lhs: unknown, rhs: unknown): boolean | null { + const result = evaluator.versionCompare(lhs, rhs); + return result !== null ? result === 0 : null; + } +} + +export class SemverGreaterThanOperator extends BinaryOperator { + binary(evaluator: Evaluator, lhs: unknown, rhs: unknown): boolean | null { + const result = evaluator.versionCompare(lhs, rhs); + return result !== null ? result > 0 : null; + } +} + +export class SemverGreaterThanOrEqualOperator extends BinaryOperator { + binary(evaluator: Evaluator, lhs: unknown, rhs: unknown): boolean | null { + const result = evaluator.versionCompare(lhs, rhs); + return result !== null ? result >= 0 : null; + } +} + +export class SemverLessThanOperator extends BinaryOperator { + binary(evaluator: Evaluator, lhs: unknown, rhs: unknown): boolean | null { + const result = evaluator.versionCompare(lhs, rhs); + return result !== null ? result < 0 : null; + } +} + +export class SemverLessThanOrEqualOperator extends BinaryOperator { + binary(evaluator: Evaluator, lhs: unknown, rhs: unknown): boolean | null { + const result = evaluator.versionCompare(lhs, rhs); + return result !== null ? result <= 0 : null; + } +} From df6b0824ccc451b61e225e3993130d77fe75c992 Mon Sep 17 00:00:00 2001 From: Jonas Alves Date: Sat, 28 Mar 2026 23:24:30 +0000 Subject: [PATCH 10/22] feat: add JsonExpr facade and AudienceMatcher --- src/__tests__/jsonexpr/jsonexpr.test.ts | 31 ++++++++++++++++++ src/__tests__/matcher.test.ts | 33 +++++++++++++++++++ src/jsonexpr/jsonexpr.ts | 42 +++++++++++++------------ src/matcher.ts | 11 +++---- 4 files changed, 91 insertions(+), 26 deletions(-) create mode 100644 src/__tests__/jsonexpr/jsonexpr.test.ts create mode 100644 src/__tests__/matcher.test.ts diff --git a/src/__tests__/jsonexpr/jsonexpr.test.ts b/src/__tests__/jsonexpr/jsonexpr.test.ts new file mode 100644 index 0000000..95ff21a --- /dev/null +++ b/src/__tests__/jsonexpr/jsonexpr.test.ts @@ -0,0 +1,31 @@ +import { describe, expect, test } from "vitest"; +import { JsonExpr } from "../../jsonexpr/jsonexpr"; + +describe("JsonExpr", () => { + const jsonExpr = new JsonExpr(); + + test("evaluateBooleanExpr with and-array", () => { + expect(jsonExpr.evaluateBooleanExpr([{ value: true }, { value: 1 }], {})).toBe(true); + expect(jsonExpr.evaluateBooleanExpr([{ value: true }, { value: false }], {})).toBe(false); + }); + + test("evaluateBooleanExpr with object expr", () => { + expect(jsonExpr.evaluateBooleanExpr({ value: true }, {})).toBe(true); + expect(jsonExpr.evaluateBooleanExpr({ value: false }, {})).toBe(false); + }); + + test("evaluateExpr returns raw value", () => { + expect(jsonExpr.evaluateExpr({ value: 42 }, {})).toBe(42); + expect(jsonExpr.evaluateExpr({ value: "hello" }, {})).toBe("hello"); + }); + + test("var operator extracts from vars", () => { + expect(jsonExpr.evaluateExpr({ var: "name" }, { name: "Alice" })).toBe("Alice"); + }); + + test("complex expression with eq and var", () => { + const expr = { eq: [{ var: "age" }, { value: 25 }] }; + expect(jsonExpr.evaluateBooleanExpr(expr, { age: 25 })).toBe(true); + expect(jsonExpr.evaluateBooleanExpr(expr, { age: 30 })).toBe(false); + }); +}); diff --git a/src/__tests__/matcher.test.ts b/src/__tests__/matcher.test.ts new file mode 100644 index 0000000..f010e2a --- /dev/null +++ b/src/__tests__/matcher.test.ts @@ -0,0 +1,33 @@ +import { describe, expect, test } from "vitest"; +import { AudienceMatcher } from "../matcher"; + +describe("AudienceMatcher", () => { + const matcher = new AudienceMatcher(); + + test("evaluates matching audience", () => { + const audience = JSON.stringify({ filter: [{ value: true }] }); + expect(matcher.evaluate(audience, {})).toBe(true); + }); + + test("evaluates non-matching audience", () => { + const audience = JSON.stringify({ filter: [{ value: false }] }); + expect(matcher.evaluate(audience, {})).toBe(false); + }); + + test("evaluates with not operator", () => { + const audience = JSON.stringify({ filter: [{ not: { value: false } }] }); + expect(matcher.evaluate(audience, {})).toBe(true); + }); + + test("returns null for invalid JSON", () => { + expect(matcher.evaluate("invalid json", {})).toBe(null); + }); + + test("returns null for missing filter", () => { + expect(matcher.evaluate(JSON.stringify({}), {})).toBe(null); + }); + + test("returns null for null filter", () => { + expect(matcher.evaluate(JSON.stringify({ filter: null }), {})).toBe(null); + }); +}); diff --git a/src/jsonexpr/jsonexpr.ts b/src/jsonexpr/jsonexpr.ts index 84414c7..8ae4562 100644 --- a/src/jsonexpr/jsonexpr.ts +++ b/src/jsonexpr/jsonexpr.ts @@ -1,22 +1,24 @@ -import { ValueOperator } from "./operators/value"; -import { AndCombinator } from "./operators/and"; -import { OrCombinator } from "./operators/or"; -import { VarOperator } from "./operators/var"; -import { NotOperator } from "./operators/not"; -import { NullOperator } from "./operators/null"; -import { MatchOperator } from "./operators/match"; -import { InOperator } from "./operators/in"; import { Evaluator } from "./evaluator"; -import { EqualsOperator } from "./operators/eq"; -import { GreaterThanOperator } from "./operators/gt"; -import { GreaterThanOrEqualOperator } from "./operators/gte"; -import { LessThanOperator } from "./operators/lt"; -import { LessThanOrEqualOperator } from "./operators/lte"; -import { SemverEqualsOperator } from "./operators/semver_eq"; -import { SemverGreaterThanOperator } from "./operators/semver_gt"; -import { SemverGreaterThanOrEqualOperator } from "./operators/semver_gte"; -import { SemverLessThanOperator } from "./operators/semver_lt"; -import { SemverLessThanOrEqualOperator } from "./operators/semver_lte"; +import { + AndCombinator, + EqualsOperator, + GreaterThanOperator, + GreaterThanOrEqualOperator, + InOperator, + LessThanOperator, + LessThanOrEqualOperator, + MatchOperator, + NotOperator, + NullOperator, + OrCombinator, + SemverEqualsOperator, + SemverGreaterThanOperator, + SemverGreaterThanOrEqualOperator, + SemverLessThanOperator, + SemverLessThanOrEqualOperator, + ValueOperator, + VarOperator, +} from "./operators"; const operators = { and: new AndCombinator(), @@ -40,12 +42,12 @@ const operators = { }; export class JsonExpr { - evaluateBooleanExpr(expr: TData[] | Record, vars: Record) { + evaluateBooleanExpr(expr: unknown, vars: Record): boolean { const evaluator = new Evaluator(operators, vars); return evaluator.booleanConvert(evaluator.evaluate(expr)); } - evaluateExpr(expr: TData[] | Record, vars: Record) { + evaluateExpr(expr: unknown, vars: Record): unknown { const evaluator = new Evaluator(operators, vars); return evaluator.evaluate(expr); } diff --git a/src/matcher.ts b/src/matcher.ts index 424988e..d2c5687 100644 --- a/src/matcher.ts +++ b/src/matcher.ts @@ -2,7 +2,9 @@ import { isObject } from "./utils"; import { JsonExpr } from "./jsonexpr/jsonexpr"; export class AudienceMatcher { - evaluate(audienceString: string, vars: Record) { + private readonly _jsonExpr = new JsonExpr(); + + evaluate(audienceString: string, vars: Record): boolean | null { try { const audience = JSON.parse(audienceString); if (audience && audience.filter) { @@ -10,12 +12,9 @@ export class AudienceMatcher { return this._jsonExpr.evaluateBooleanExpr(audience.filter, vars); } } - } catch (e) { - console.error(e); + } catch { + // invalid JSON } - return null; } - - _jsonExpr = new JsonExpr(); } From dc6d8e43090b274d18b6d2f036be79c1a303cf97 Mon Sep 17 00:00:00 2001 From: Jonas Alves Date: Sat, 28 Mar 2026 23:26:39 +0000 Subject: [PATCH 11/22] feat: add HTTP client with retry logic and exponential backoff --- src/__tests__/client.test.ts | 185 +++++++++++++++++++++++++++++++++++ src/client.ts | 168 ++++++++----------------------- 2 files changed, 226 insertions(+), 127 deletions(-) create mode 100644 src/__tests__/client.test.ts diff --git a/src/__tests__/client.test.ts b/src/__tests__/client.test.ts new file mode 100644 index 0000000..161c2cd --- /dev/null +++ b/src/__tests__/client.test.ts @@ -0,0 +1,185 @@ +import { describe, expect, test, vi, beforeEach, afterEach } from "vitest"; +import { Client } from "../client"; +import type { ClientOptions } from "../types"; + +const defaultOpts: ClientOptions = { + agent: "test-agent", + apiKey: "test-api-key", + application: "test-app", + endpoint: "https://test.absmartly.io/v1", + environment: "test", +}; + +describe("Client", () => { + describe("constructor validation", () => { + test("throws for missing apiKey", () => { + const opts = { ...defaultOpts, apiKey: undefined } as unknown as ClientOptions; + expect(() => new Client(opts)).toThrow("Missing 'apiKey' in options argument"); + }); + + test("throws for missing endpoint", () => { + const opts = { ...defaultOpts, endpoint: undefined } as unknown as ClientOptions; + expect(() => new Client(opts)).toThrow("Missing 'endpoint' in options argument"); + }); + + test("throws for missing environment", () => { + const opts = { ...defaultOpts, environment: undefined } as unknown as ClientOptions; + expect(() => new Client(opts)).toThrow("Missing 'environment' in options argument"); + }); + + test("throws for missing application", () => { + const opts = { ...defaultOpts, application: undefined } as unknown as ClientOptions; + expect(() => new Client(opts)).toThrow("Missing 'application' in options argument"); + }); + + test("throws for empty apiKey", () => { + const opts = { ...defaultOpts, apiKey: "" }; + expect(() => new Client(opts)).toThrow("Invalid 'apiKey' in options argument"); + }); + + test("accepts ApplicationObject", () => { + const opts = { ...defaultOpts, application: { name: "my-app", version: "1.0.0" } }; + const client = new Client(opts); + expect(client.getApplication()).toEqual({ name: "my-app", version: "1.0.0" }); + }); + + test("converts string application to ApplicationObject", () => { + const client = new Client(defaultOpts); + expect(client.getApplication()).toEqual({ name: "test-app", version: 0 }); + }); + }); + + describe("accessors", () => { + test("getAgent", () => { + const client = new Client(defaultOpts); + expect(client.getAgent()).toBe("test-agent"); + }); + + test("getEnvironment", () => { + const client = new Client(defaultOpts); + expect(client.getEnvironment()).toBe("test"); + }); + }); + + describe("getContext", () => { + beforeEach(() => { + vi.stubGlobal("fetch", vi.fn()); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + test("makes GET request to /context", async () => { + const mockResponse = { ok: true, json: () => Promise.resolve({ experiments: [] }) }; + (globalThis.fetch as ReturnType).mockResolvedValue(mockResponse); + + const client = new Client({ ...defaultOpts, retries: 0, timeout: 1000 }); + await client.getContext(); + + expect(globalThis.fetch).toHaveBeenCalledTimes(1); + const [url] = (globalThis.fetch as ReturnType).mock.calls[0]!; + expect(url).toContain("/context"); + expect(url).toContain("application=test-app"); + expect(url).toContain("environment=test"); + }); + }); + + describe("publish", () => { + beforeEach(() => { + vi.stubGlobal("fetch", vi.fn()); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + test("makes PUT request to /context with auth headers", async () => { + const mockResponse = { ok: true, json: () => Promise.resolve({}) }; + (globalThis.fetch as ReturnType).mockResolvedValue(mockResponse); + + const client = new Client({ ...defaultOpts, retries: 0, timeout: 1000 }); + await client.publish({ + units: [{ type: "session_id", uid: "abc" }], + publishedAt: 1000, + hashed: true, + sdkVersion: "2.0.0", + }); + + const [, opts] = (globalThis.fetch as ReturnType).mock.calls[0]!; + expect(opts.method).toBe("PUT"); + expect(opts.headers["X-API-Key"]).toBe("test-api-key"); + expect(opts.headers["X-Agent"]).toBe("test-agent"); + expect(opts.headers["X-Environment"]).toBe("test"); + }); + + test("omits empty goals and exposures arrays", async () => { + const mockResponse = { ok: true, json: () => Promise.resolve({}) }; + (globalThis.fetch as ReturnType).mockResolvedValue(mockResponse); + + const client = new Client({ ...defaultOpts, retries: 0, timeout: 1000 }); + await client.publish({ + units: [{ type: "session_id", uid: "abc" }], + publishedAt: 1000, + hashed: true, + sdkVersion: "2.0.0", + goals: [], + exposures: [], + }); + + const [, opts] = (globalThis.fetch as ReturnType).mock.calls[0]!; + const body = JSON.parse(opts.body); + expect(body.goals).toBeUndefined(); + expect(body.exposures).toBeUndefined(); + }); + }); + + describe("retry logic", () => { + beforeEach(() => { + vi.useFakeTimers(); + vi.stubGlobal("fetch", vi.fn()); + }); + + afterEach(() => { + vi.useRealTimers(); + vi.restoreAllMocks(); + }); + + test("retries on server error", async () => { + const failResponse = { ok: false, status: 500, statusText: "Server Error", text: () => Promise.resolve("") }; + const successResponse = { ok: true, json: () => Promise.resolve({ data: "ok" }) }; + + (globalThis.fetch as ReturnType) + .mockResolvedValueOnce(failResponse) + .mockResolvedValueOnce(successResponse); + + const client = new Client({ ...defaultOpts, retries: 3, timeout: 10000 }); + const promise = client.getContext(); + + await vi.runAllTimersAsync(); + const result = await promise; + + expect(result).toEqual({ data: "ok" }); + expect(globalThis.fetch).toHaveBeenCalledTimes(2); + }); + + test("does not retry on 4xx error", async () => { + const failResponse = { + ok: false, + status: 400, + statusText: "Bad Request", + text: () => Promise.resolve("bad request"), + }; + + (globalThis.fetch as ReturnType).mockResolvedValue(failResponse); + + const client = new Client({ ...defaultOpts, retries: 3, timeout: 10000 }); + const promise = client.getContext(); + + await expect(promise).rejects.toThrow("bad request"); + expect(globalThis.fetch).toHaveBeenCalledTimes(1); + + await vi.runAllTimersAsync(); + }); + }); +}); diff --git a/src/client.ts b/src/client.ts index 3f934bb..1dd54e7 100644 --- a/src/client.ts +++ b/src/client.ts @@ -1,61 +1,21 @@ -import fetch from "./fetch"; // eslint-disable-line no-shadow -// eslint-disable-next-line no-shadow -import { AbortController } from "./abort"; -// eslint-disable-next-line no-shadow import { AbortError, RetryError, TimeoutError } from "./errors"; - -import { AbortSignal as ABsmartlyAbortSignal } from "./abort-controller-shim"; -import { ContextOptions, ContextParams } from "./context"; -import { PublishParams } from "./publisher"; - -export type FetchResponse = { - status: number; - ok: boolean; - text: () => Promise; - statusText: string; - json: () => Promise; -}; - -export type ClientRequestOptions = { - query?: Record; - path: string; - method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS"; - body?: Record; - auth?: boolean; - signal?: AbortSignal | ABsmartlyAbortSignal; - timeout?: number; -}; - -export type ApplicationObject = { name: string; version: number | string }; - -export type ClientOptions = { - agent?: string; - apiKey: string; - application: string | ApplicationObject; - endpoint: string; - environment: string; - retries?: number; - timeout?: number; - keepalive?: boolean; -}; - -type NormalizedClientOptions = Omit, "application"> & { - application: ApplicationObject; -}; - -export default class Client { +import type { + ApplicationObject, + ClientOptions, + ClientRequestOptions, + ContextParams, + NormalizedClientOptions, + PublishParams, +} from "./types"; + +export class Client { private readonly _opts: NormalizedClientOptions; private readonly _delay: number; constructor(opts: ClientOptions) { const merged: Record = Object.assign( - { - agent: "javascript-client", - retries: 5, - timeout: 3000, - keepalive: true, - }, - opts + { agent: "javascript-client", retries: 5, timeout: 3000, keepalive: true }, + opts, ); for (const key of ["agent", "application", "apiKey", "endpoint", "environment"]) { @@ -63,9 +23,7 @@ export default class Client { const value = merged[key]; if (typeof value !== "string" || (value as string).length === 0) { if (key === "application") { - if (value !== null && typeof value === "object" && "name" in (value as object)) { - continue; - } + if (value !== null && typeof value === "object" && "name" in (value as object)) continue; } throw new Error(`Invalid '${key}' in options argument`); } @@ -75,17 +33,14 @@ export default class Client { } if (typeof merged.application === "string") { - merged.application = { - name: merged.application, - version: 0, - }; + merged.application = { name: merged.application, version: 0 }; } this._opts = merged as unknown as NormalizedClientOptions; this._delay = 50; } - getContext(options?: Partial) { + getContext(options?: Partial): Promise { return this.getUnauthed({ ...options, path: "/context", @@ -96,20 +51,12 @@ export default class Client { }); } - createContext(params: ContextParams, options: ContextOptions) { - const body = { - units: params.units, - }; - - return this.post({ - ...options, - path: "/context", - body, - }); + createContext(params: ContextParams): Promise { + return this.post({ path: "/context", body: { units: params.units } }); } - publish(params: PublishParams, options?: ClientRequestOptions) { - const body: PublishParams = { + publish(params: PublishParams, options?: ClientRequestOptions): Promise { + const body: Record = { units: params.units, hashed: params.hashed, publishedAt: params.publishedAt || Date.now(), @@ -119,29 +66,23 @@ export default class Client { if (Array.isArray(params.goals) && params.goals.length > 0) { body.goals = params.goals; } - if (Array.isArray(params.exposures) && params.exposures.length > 0) { body.exposures = params.exposures; } - if (Array.isArray(params.attributes) && params.attributes.length > 0) { body.attributes = params.attributes; } - return this.put({ - ...options, - path: "/context", - body, - }); + return this.put({ ...options, path: "/context", body }); } - request(options: ClientRequestOptions) { + request(options: ClientRequestOptions): Promise { let url = `${this._opts.endpoint}${options.path}`; if (options.query) { const keys = Object.keys(options.query); if (keys.length > 0) { const encoded = keys - .map((k) => (options.query ? `${k}=${encodeURIComponent(options.query[k])}` : null)) + .map((k) => (options.query ? `${k}=${encodeURIComponent(options.query[k]!)}` : null)) .join("&"); url = `${url}?${encoded}`; } @@ -149,8 +90,8 @@ export default class Client { const controller = new AbortController(); - const tryOnce = () => { - const opts: Record = { + const tryOnce = (): Promise => { + const opts: RequestInit = { method: options.method, body: options.body !== undefined ? JSON.stringify(options.body, null, 0) : undefined, signal: controller.signal, @@ -164,34 +105,27 @@ export default class Client { "X-Agent": this._opts.agent, "X-Environment": this._opts.environment, "X-Application": this._opts.application.name, - "X-Application-Version": this._opts.application.version, + "X-Application-Version": String(this._opts.application.version), }; } - return fetch(url, opts).then((response: FetchResponse) => { + return fetch(url, opts).then((response: Response) => { if (!response.ok) { const bail = response.status >= 400 && response.status < 500; return response.text().then((text: string) => { const error: Error & { _bail?: boolean } = new Error( - text !== null && text.length > 0 ? text : response.statusText + text !== null && text.length > 0 ? text : response.statusText, ); error._bail = bail; - return Promise.reject(error); }); } - return response.json(); }); }; type WaitFn = ((ms: number) => Promise) & { reject?: (reason: AbortError) => void }; - type TryWithFn = (( - retries: number, - timeout: number, - tries?: number, - waited?: number - ) => ReturnType) & { + type TryWithFn = ((retries: number, timeout: number, tries?: number, waited?: number) => Promise) & { timedout?: boolean; }; @@ -212,24 +146,15 @@ export default class Client { delete tryWith.timedout; return tryOnce().catch((reason: Error & { _bail?: boolean }) => { - console.warn(reason); - - if (reason._bail || retries <= 0) { - throw new Error(reason.message); - } else if (tries >= retries) { - throw new RetryError(tries, reason, url); - } else if (waited >= timeout || reason.name === "AbortError") { - if (tryWith.timedout) { - throw new TimeoutError(timeout); - } - + if (reason._bail || retries <= 0) throw new Error(reason.message); + if (tries >= retries) throw new RetryError(tries, reason, url); + if (waited >= timeout || reason.name === "AbortError") { + if (tryWith.timedout) throw new TimeoutError(timeout); throw reason; } let delay = (1 << tries) * this._delay + 0.5 * Math.random() * this._delay; - if (waited + delay > timeout) { - delay = timeout - waited; - } + if (waited + delay > timeout) delay = timeout - waited; return wait(delay).then(() => tryWith(retries, timeout, tries + 1, waited + delay)); }); @@ -253,7 +178,7 @@ export default class Client { ? setTimeout(() => { tryWith.timedout = true; abort(); - }, timeout) + }, timeout) : 0; const finalCleanUp = () => { @@ -264,7 +189,7 @@ export default class Client { }; return tryWith(this._opts.retries ?? 5, this._opts.timeout ?? 3000) - .then((value: string) => { + .then((value) => { finalCleanUp(); return value; }) @@ -274,20 +199,12 @@ export default class Client { }); } - post(options: ClientRequestOptions) { - return this.request({ - ...options, - auth: true, - method: "POST", - }); + post(options: ClientRequestOptions): Promise { + return this.request({ ...options, auth: true, method: "POST" }); } - put(options: ClientRequestOptions) { - return this.request({ - ...options, - auth: true, - method: "PUT", - }); + put(options: ClientRequestOptions): Promise { + return this.request({ ...options, auth: true, method: "PUT" }); } getAgent(): string { @@ -302,10 +219,7 @@ export default class Client { return this._opts.environment; } - getUnauthed(options: ClientRequestOptions) { - return this.request({ - ...options, - method: "GET", - }); + getUnauthed(options: ClientRequestOptions): Promise { + return this.request({ ...options, method: "GET" }); } } From 4ac61c763a3040dd89a814e0d53d0b520737310a Mon Sep 17 00:00:00 2001 From: Jonas Alves Date: Sat, 28 Mar 2026 23:26:39 +0000 Subject: [PATCH 12/22] feat: add ContextDataProvider and ContextPublisher --- src/__tests__/provider.test.ts | 15 +++++++++++++++ src/__tests__/publisher.test.ts | 20 ++++++++++++++++++++ src/provider.ts | 11 +++++++---- src/publisher.ts | 18 +++++------------- 4 files changed, 47 insertions(+), 17 deletions(-) create mode 100644 src/__tests__/provider.test.ts create mode 100644 src/__tests__/publisher.test.ts diff --git a/src/__tests__/provider.test.ts b/src/__tests__/provider.test.ts new file mode 100644 index 0000000..b530a25 --- /dev/null +++ b/src/__tests__/provider.test.ts @@ -0,0 +1,15 @@ +import { describe, expect, test, vi } from "vitest"; +import { ContextDataProvider } from "../provider"; + +describe("ContextDataProvider", () => { + test("delegates to sdk.getClient().getContext()", async () => { + const mockGetContext = vi.fn().mockResolvedValue({ experiments: [] }); + const mockSdk = { getClient: () => ({ getContext: mockGetContext }) }; + + const provider = new ContextDataProvider(); + const result = await provider.getContextData(mockSdk, { path: "/test" }); + + expect(mockGetContext).toHaveBeenCalledWith({ path: "/test" }); + expect(result).toEqual({ experiments: [] }); + }); +}); diff --git a/src/__tests__/publisher.test.ts b/src/__tests__/publisher.test.ts new file mode 100644 index 0000000..85e8634 --- /dev/null +++ b/src/__tests__/publisher.test.ts @@ -0,0 +1,20 @@ +import { describe, expect, test, vi } from "vitest"; +import { ContextPublisher } from "../publisher"; + +describe("ContextPublisher", () => { + test("delegates to sdk.getClient().publish()", async () => { + const mockPublish = vi.fn().mockResolvedValue({}); + const mockSdk = { getClient: () => ({ publish: mockPublish }) }; + const request = { + units: [{ type: "session_id", uid: "abc" }], + publishedAt: 1000, + hashed: true, + sdkVersion: "2.0.0", + }; + + const publisher = new ContextPublisher(); + await publisher.publish(request, mockSdk, {}, { path: "/test" }); + + expect(mockPublish).toHaveBeenCalledWith(request, { path: "/test" }); + }); +}); diff --git a/src/provider.ts b/src/provider.ts index 30dac20..55c90cb 100644 --- a/src/provider.ts +++ b/src/provider.ts @@ -1,8 +1,11 @@ -import SDK from "./sdk"; -import { ClientRequestOptions } from "./client"; +import type { ClientRequestOptions, ContextData } from "./types"; + +interface SDKLike { + getClient(): { getContext(options?: Partial): Promise }; +} export class ContextDataProvider { - getContextData(sdk: SDK, requestOptions?: Partial) { - return sdk.getClient().getContext(requestOptions); + getContextData(sdk: SDKLike, requestOptions?: Partial): Promise { + return sdk.getClient().getContext(requestOptions) as Promise; } } diff --git a/src/publisher.ts b/src/publisher.ts index 79c5f14..c5500c9 100644 --- a/src/publisher.ts +++ b/src/publisher.ts @@ -1,19 +1,11 @@ -import Context, { Attribute, Exposure, Goal, Unit } from "./context"; -import SDK from "./sdk"; -import { ClientRequestOptions } from "./client"; +import type { ClientRequestOptions, PublishParams } from "./types"; -export type PublishParams = { - units: Unit[]; - publishedAt: number; - hashed: boolean; - sdkVersion: string; - attributes?: Attribute[]; - goals?: Goal[]; - exposures?: Exposure[]; -}; +interface SDKLike { + getClient(): { publish(request: PublishParams, options?: ClientRequestOptions): Promise }; +} export class ContextPublisher { - publish(request: PublishParams, sdk: SDK, _: Context, requestOptions?: ClientRequestOptions) { + publish(request: PublishParams, sdk: SDKLike, _context: unknown, requestOptions?: ClientRequestOptions): Promise { return sdk.getClient().publish(request, requestOptions); } } From a484d1d9b135e2637f2cabb041f563d495ed9db5 Mon Sep 17 00:00:00 2001 From: Jonas Alves Date: Sat, 28 Mar 2026 23:47:16 +0000 Subject: [PATCH 13/22] feat: add Context class with experiment assignment and lifecycle management --- src/__tests__/context.test.ts | 2908 +++++++++++++++++++++++++++++++++ src/context.ts | 269 +-- 2 files changed, 2991 insertions(+), 186 deletions(-) create mode 100644 src/__tests__/context.test.ts diff --git a/src/__tests__/context.test.ts b/src/__tests__/context.test.ts new file mode 100644 index 0000000..5bd8173 --- /dev/null +++ b/src/__tests__/context.test.ts @@ -0,0 +1,2908 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import { Context } from "../context"; +import { ContextPublisher } from "../publisher"; +import { ContextDataProvider } from "../provider"; +import { hashUnit } from "../hashing"; +import { SDK_VERSION } from "../version"; + +function clone(obj: T): T { + return JSON.parse(JSON.stringify(obj)); +} + +describe("Context", () => { + const contextParams = { + units: { + session_id: "e791e240fcd3df7d238cfc285f475e8152fcc0ec", + user_id: 12317303, + }, + }; + + const publishUnits = Object.entries(contextParams.units).map((x) => ({ type: x[0], uid: hashUnit(x[1]) })); + + const units = { + session_id: "e791e240fcd3df7d238cfc285f475e8152fcc0ec", + user_id: "123456789", + email: "bleh@absmartly.com", + }; + + const getContextResponse = { + experiments: [ + { + id: 1, + name: "exp_test_ab", + iteration: 1, + unitType: "session_id", + seedHi: 3603515, + seedLo: 233373850, + split: [0.5, 0.5], + trafficSeedHi: 449867249, + trafficSeedLo: 455443629, + trafficSplit: [0.0, 1.0], + fullOnVariant: 0, + applications: [{ name: "website" }], + variants: [ + { name: "A", config: null }, + { name: "B", config: '{"banner.border":1,"banner.size":"large"}' }, + ], + audience: null, + audienceStrict: false, + customFieldValues: null, + }, + { + id: 2, + name: "exp_test_abc", + iteration: 1, + unitType: "session_id", + seedHi: 55006150, + seedLo: 47189152, + split: [0.34, 0.33, 0.33], + trafficSeedHi: 705671872, + trafficSeedLo: 212903484, + trafficSplit: [0.0, 1.0], + fullOnVariant: 0, + applications: [{ name: "website" }], + variants: [ + { name: "A", config: null }, + { name: "B", config: '{"button.color":"blue"}' }, + { name: "C", config: '{"button.color":"red"}' }, + ], + audience: "", + audienceStrict: false, + customFieldValues: [ + { name: "country", value: "US,PT,ES,DE,FR", type: "string" }, + { name: "json_object", value: '{"123":1,"456":0}', type: "json" }, + { name: "json_array", value: '["hello", "world"]', type: "json" }, + { name: "json_number", value: "123", type: "json" }, + { name: "json_string", value: '"hello"', type: "json" }, + { name: "json_boolean", value: "true", type: "json" }, + { name: "json_null", value: "null", type: "json" }, + { name: "json_invalid", value: "invalid", type: "json" }, + ], + }, + { + id: 3, + name: "exp_test_not_eligible", + iteration: 1, + unitType: "user_id", + seedHi: 503266407, + seedLo: 144942754, + split: [0.34, 0.33, 0.33], + trafficSeedHi: 87768905, + trafficSeedLo: 511357582, + trafficSplit: [0.99, 0.01], + fullOnVariant: 0, + applications: [{ name: "website" }], + variants: [ + { name: "A", config: null }, + { name: "B", config: '{"card.width":"80%"}' }, + { name: "C", config: '{"card.width":"75%"}' }, + ], + audience: "{}", + audienceStrict: false, + customFieldValues: null, + }, + { + id: 4, + name: "exp_test_fullon", + iteration: 1, + unitType: "session_id", + seedHi: 856061641, + seedLo: 990838475, + split: [0.25, 0.25, 0.25, 0.25], + trafficSeedHi: 360868579, + trafficSeedLo: 330937933, + trafficSplit: [0.0, 1.0], + fullOnVariant: 2, + applications: [{ name: "website" }], + variants: [ + { name: "A", config: null }, + { name: "B", config: '{"submit.color":"red","submit.shape":"circle"}' }, + { name: "C", config: '{"submit.color":"blue","submit.shape":"rect"}' }, + { name: "D", config: '{"submit.color":"green","submit.shape":"square"}' }, + ], + audience: "null", + audienceStrict: false, + customFieldValues: null, + }, + { + id: 5, + name: "exp_test_custom_fields", + iteration: 1, + unitType: "session_id", + seedHi: 9372617, + seedLo: 121364805, + split: [0.5, 0.5], + trafficSeedHi: 318746944, + trafficSeedLo: 359812364, + trafficSplit: [0.0, 1.0], + fullOnVariant: 0, + applications: [{ name: "website" }], + variants: [ + { name: "A", config: null }, + { name: "B", config: '{"submit.size":"sm"}' }, + ], + audience: null, + audienceStrict: false, + customFieldValues: [ + { name: "country", value: "US,PT,ES", type: "string" }, + { name: "languages", value: "en-US,en-GB,pt-PT,pt-BR,es-ES,es-MX", type: "string" }, + { name: "text_field", value: "hello text", type: "text" }, + { name: "string_field", value: "hello string", type: "string" }, + { name: "number_field", value: "123", type: "number" }, + { name: "boolean_field", value: "true", type: "boolean" }, + { name: "false_boolean_field", value: "false", type: "boolean" }, + { name: "invalid_type_field", value: "invalid", type: "invalid" }, + ], + }, + ], + }; + + const refreshContextResponse = { + ...getContextResponse, + experiments: [ + { + id: 6, + name: "exp_test_new", + iteration: 2, + unitType: "session_id", + seedHi: 934590467, + seedLo: 714771373, + split: [0.5, 0.5], + trafficSeedHi: 940553836, + trafficSeedLo: 270705624, + trafficSplit: [0.0, 1.0], + fullOnVariant: 1, + applications: [{ name: "website" }], + variants: [ + { name: "A", config: null }, + { name: "B", config: '{"show-modal":true}' }, + ], + audience: null, + audienceStrict: false, + customFieldValues: null, + }, + ...getContextResponse.experiments, + ], + }; + + const audienceContextResponse = { + ...getContextResponse, + experiments: getContextResponse.experiments.map((x) => { + if (x.name === "exp_test_ab") { + return { + ...x, + audience: JSON.stringify({ + filter: [{ gte: [{ var: "age" }, { value: 20 }] }], + }), + }; + } + return x; + }), + }; + + const audienceStrictContextResponse = { + ...audienceContextResponse, + experiments: audienceContextResponse.experiments.map((x) => { + if (x.name === "exp_test_ab") { + return { + ...x, + audienceStrict: true, + variants: x.variants.map((v) => { + if (v.name === "A") { + return { name: "A", config: '{"banner.size":"tiny"}' }; + } + return v; + }), + }; + } + return x; + }), + }; + + const expectedVariants: Record = { + exp_test_ab: 1, + exp_test_abc: 2, + exp_test_not_eligible: 0, + exp_test_fullon: 2, + exp_test_new: 1, + exp_test_custom_fields: 1, + }; + + const lowestIdConflictingKeyContextResponse = { + ...getContextResponse, + experiments: getContextResponse.experiments.map((e) => { + if (e.name === "exp_test_ab") { + return { + ...e, + id: 99, + variants: e.variants.map((v, i) => { + if (i === expectedVariants[e.name]) { + return { ...v, config: JSON.stringify({ icon: "arrow" }) }; + } + return v; + }), + }; + } + if (e.name === "exp_test_abc") { + return { + ...e, + id: 1, + variants: e.variants.map((v, i) => { + if (i === expectedVariants[e.name]) { + return { ...v, config: JSON.stringify({ icon: "circle" }) }; + } + return v; + }), + }; + } + return e; + }), + }; + + const disjointedContextResponse = { + ...getContextResponse, + experiments: getContextResponse.experiments.map((exp) => { + if (exp.name === "exp_test_ab") { + return { + ...exp, + audienceStrict: true, + audience: JSON.stringify({ + filter: [{ gte: [{ var: "age" }, { value: 20 }] }], + }), + variants: exp.variants.map((v, i) => { + if (i === expectedVariants[exp.name]) { + return { ...v, config: JSON.stringify({ icon: "arrow" }) }; + } + return v; + }), + }; + } + if (exp.name === "exp_test_abc") { + return { + ...exp, + audienceStrict: true, + audience: JSON.stringify({ + filter: [{ lt: [{ var: "age" }, { value: 20 }] }], + }), + variants: exp.variants.map((variant, i) => { + if (i === expectedVariants[exp.name]) { + return { ...variant, config: JSON.stringify({ icon: "circle" }) }; + } + return variant; + }), + }; + } + return exp; + }), + }; + + const expectedVariables: Record = { + "banner.border": 1, + "banner.size": "large", + "button.color": "red", + "submit.color": "blue", + "submit.shape": "rect", + "show-modal": true, + "submit.size": "sm", + }; + + const variableExperiments: Record = { + "banner.border": ["exp_test_ab"], + "banner.size": ["exp_test_ab"], + "button.color": ["exp_test_abc"], + "card.width": ["exp_test_not_eligible"], + "submit.color": ["exp_test_fullon"], + "submit.shape": ["exp_test_fullon"], + "submit.size": ["exp_test_custom_fields"], + "show-modal": ["exp_test_new"], + }; + + const defaultEventLogger = vi.fn(); + + const publisher = { + publish: vi.fn(), + } as unknown as ContextPublisher & { publish: ReturnType }; + + const provider = { + getContextData: vi.fn(), + } as unknown as ContextDataProvider & { getContextData: ReturnType }; + + const client = { + getAgent: vi.fn().mockReturnValue("absmartly-javascript-sdk"), + getApplication: vi.fn().mockReturnValue({ name: "website", version: 0 }), + getEnvironment: vi.fn().mockReturnValue("production"), + }; + + const sdk = { + getContextPublisher: vi.fn().mockReturnValue(publisher), + getContextDataProvider: vi.fn().mockReturnValue(provider), + getClient: vi.fn().mockReturnValue(client), + getEventLogger: vi.fn().mockReturnValue(defaultEventLogger), + }; + + const contextOptions = { + publishDelay: -1, + refreshPeriod: 0, + }; + + const timeOrigin = 1611141535729; + + beforeEach(() => { + vi.spyOn(Date, "now").mockImplementation(() => timeOrigin); + vi.clearAllMocks(); + sdk.getContextPublisher.mockReturnValue(publisher); + sdk.getContextDataProvider.mockReturnValue(provider); + sdk.getClient.mockReturnValue(client); + sdk.getEventLogger.mockReturnValue(defaultEventLogger); + client.getAgent.mockReturnValue("absmartly-javascript-sdk"); + client.getApplication.mockReturnValue({ name: "website", version: 0 }); + client.getEnvironment.mockReturnValue("production"); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + describe("Context", () => { + it("should be ready with data", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.isReady()).toEqual(true); + expect(context.isFailed()).toEqual(false); + + await context.ready(); + expect(context.isReady()).toEqual(true); + expect(context.data()).toStrictEqual(getContextResponse); + expect(context.eventLogger()).toBe(defaultEventLogger); + expect(context.provider()).toBe(provider); + expect(context.publisher()).toBe(publisher); + }); + + it("should use custom publisher, dataProvider and eventLogger", async () => { + const customPublisher = { publish: vi.fn() } as unknown as ContextPublisher; + const customDataProvider = { getContextData: vi.fn() } as unknown as ContextDataProvider; + const customEventLogger = vi.fn(); + + const context = new Context( + sdk, + { + ...contextOptions, + publisher: customPublisher, + dataProvider: customDataProvider, + eventLogger: customEventLogger, + }, + contextParams, + getContextResponse as any + ); + expect(context.isReady()).toEqual(true); + expect(context.isFailed()).toEqual(false); + + await context.ready(); + expect(context.isReady()).toEqual(true); + expect(context.data()).toStrictEqual(getContextResponse); + expect(context.eventLogger()).toBe(customEventLogger); + expect(context.provider()).toBe(customDataProvider); + expect(context.publisher()).toBe(customPublisher); + }); + + it("should become ready and call handler", async () => { + const context = new Context(sdk, contextOptions, contextParams, Promise.resolve(getContextResponse as any)); + expect(context.isReady()).toEqual(false); + expect(context.isFailed()).toEqual(false); + + await context.ready(); + expect(context.isReady()).toEqual(true); + expect(context.data()).toStrictEqual(getContextResponse); + expect(context.eventLogger()).toBe(defaultEventLogger); + expect(context.provider()).toBe(provider); + expect(context.publisher()).toBe(publisher); + }); + + it("should become ready and failed, and call handler on failure", async () => { + const context = new Context(sdk, contextOptions, contextParams, Promise.reject("bad request error text") as any); + expect(context.isReady()).toEqual(false); + expect(context.isFailed()).toEqual(false); + + await context.ready(); + expect(context.isReady()).toEqual(true); + expect(context.isFailed()).toEqual(true); + expect(context.data()).toStrictEqual({}); + expect(context.eventLogger()).toBe(defaultEventLogger); + expect(context.provider()).toBe(provider); + expect(context.publisher()).toBe(publisher); + }); + + it("should call event logger on error", async () => { + defaultEventLogger.mockClear(); + + const context = new Context(sdk, contextOptions, contextParams, Promise.reject("bad request error text") as any); + await context.ready(); + expect(defaultEventLogger).toHaveBeenCalledTimes(1); + expect(defaultEventLogger).toHaveBeenCalledWith(context, "error", "bad request error text"); + }); + + it("should call event logger on success", async () => { + defaultEventLogger.mockClear(); + + const context = new Context(sdk, contextOptions, contextParams, Promise.resolve(getContextResponse as any)); + await context.ready(); + expect(defaultEventLogger).toHaveBeenCalledTimes(1); + expect(defaultEventLogger).toHaveBeenCalledWith(context, "ready", getContextResponse); + }); + + it("should call event logger on pre-fetched experiment data", async () => { + defaultEventLogger.mockClear(); + + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + await context.ready(); + expect(defaultEventLogger).toHaveBeenCalledTimes(1); + expect(defaultEventLogger).toHaveBeenCalledWith(context, "ready", getContextResponse); + }); + + it("should throw when not ready", () => { + const context = new Context(sdk, contextOptions, contextParams, Promise.resolve(getContextResponse as any)); + expect(context.isReady()).toEqual(false); + expect(context.isFailed()).toEqual(false); + expect(context.isFinalized()).toEqual(false); + + expect(() => context.data()).toThrow(); + expect(() => context.treatment("test")).toThrow(); + expect(() => context.peek("test")).toThrow(); + expect(() => context.experiments()).toThrow(); + expect(() => context.variableKeys()).toThrow(); + expect(() => context.variableValue("a", "17")).toThrow(); + expect(() => context.peekVariableValue("a", "17")).toThrow(); + }); + + it("should load experiment data", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + + expect(context.experiments()).toEqual(getContextResponse.experiments.map((x) => x.name)); + for (const experiment of getContextResponse.experiments) { + expect(context.peek(experiment.name)).toEqual(expectedVariants[experiment.name]); + expect(context.treatment(experiment.name)).toEqual(expectedVariants[experiment.name]); + } + expect(context.data()).toEqual(getContextResponse); + }); + }); + + describe("unit()", () => { + it("should set a unit", () => { + const context = new Context(sdk, contextOptions, { units: {} }, getContextResponse as any); + + context.units(units); + + for (const [key, value] of Object.entries(units)) { + expect(context.getUnit(key)).toEqual(value); + } + + expect(context.getUnits()).toEqual(units); + }); + + it("should throw on duplicate unit type set", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.isReady()).toEqual(true); + + expect(() => context.unit("session_id", "new_id")).toThrow(); + expect(() => context.unit("session_id", "e791e240fcd3df7d238cfc285f475e8152fcc0ec")).not.toThrow(); + }); + + it("should throw on invalid uid", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.isReady()).toEqual(true); + + expect(() => context.unit("session_id", "")).toThrow(); + expect(() => context.unit("session_id", null as any)).toThrow(); + expect(() => context.unit("session_id", undefined as any)).toThrow(); + expect(() => context.unit("session_id", true as any)).toThrow(); + expect(() => context.unit("session_id", {} as any)).toThrow(); + expect(() => context.unit("session_id", [] as any)).toThrow(); + }); + + it("should be callable before ready()", async () => { + const context = new Context(sdk, contextOptions, { units: {} } as any, Promise.resolve(getContextResponse as any)); + + context.units(contextParams.units); + + await context.ready(); + expect(context.isReady()).toEqual(true); + + context.treatment("exp_test_ab"); + + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + vi.spyOn(Date, "now").mockImplementation(() => timeOrigin + 100); + + await context.publish(); + expect(publisher.publish).toHaveBeenCalledTimes(1); + expect(publisher.publish).toHaveBeenCalledWith( + { + publishedAt: 1611141535829, + units: publishUnits, + hashed: true, + sdkVersion: SDK_VERSION, + exposures: [ + { + id: 1, + name: "exp_test_ab", + unit: "session_id", + exposedAt: 1611141535729, + variant: 1, + assigned: true, + eligible: true, + overridden: false, + fullOn: false, + custom: false, + audienceMismatch: false, + }, + ], + }, + sdk, + context, + undefined + ); + }); + + it("should throw after finalized() call", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + context.treatment("exp_test_ab"); + + expect(context.pending()).toEqual(1); + + const finalizePromise = context.finalize(); + + expect(context.isFinalizing()).toEqual(true); + expect(() => context.unit("test", "test")).toThrow(); + + await finalizePromise; + + expect(() => context.unit("test", "test")).toThrow(); + }); + }); + + describe("getAttribute()", () => { + it("should get the last set attribute", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + + context.attribute("attr1", "value1"); + context.attribute("attr1", "value2"); + + expect(context.getAttribute("attr1")).toEqual("value2"); + }); + }); + + describe("attribute()", () => { + it("should set an attribute", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + + context.attribute("attr1", "value1"); + context.attributes({ + attr2: "value2", + attr3: 15, + }); + + expect(context.getAttribute("attr1")).toEqual("value1"); + expect(context.getAttributes()).toEqual({ + attr1: "value1", + attr2: "value2", + attr3: 15, + }); + }); + + it("should be callable before ready()", async () => { + const context = new Context(sdk, contextOptions, contextParams, Promise.resolve(getContextResponse as any)); + expect(context.isReady()).toEqual(false); + expect(context.isFailed()).toEqual(false); + expect(context.isFinalized()).toEqual(false); + + context.attribute("attr1", "value1"); + context.attributes({ + attr2: "value2", + attr3: 3, + }); + + expect(context.getAttribute("attr1")).toEqual("value1"); + expect(context.getAttributes()).toEqual({ + attr1: "value1", + attr2: "value2", + attr3: 3, + }); + + await context.ready(); + expect(context.isReady()).toEqual(true); + + context.treatment("exp_test_ab"); + + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + vi.spyOn(Date, "now").mockImplementation(() => timeOrigin + 100); + + await context.publish(); + expect(publisher.publish).toHaveBeenCalledTimes(1); + expect(publisher.publish).toHaveBeenCalledWith( + { + publishedAt: 1611141535829, + units: publishUnits, + hashed: true, + sdkVersion: SDK_VERSION, + exposures: [ + { + id: 1, + name: "exp_test_ab", + unit: "session_id", + exposedAt: 1611141535729, + variant: 1, + assigned: true, + eligible: true, + overridden: false, + fullOn: false, + custom: false, + audienceMismatch: false, + }, + ], + attributes: [ + { name: "attr1", setAt: 1611141535729, value: "value1" }, + { name: "attr2", setAt: 1611141535729, value: "value2" }, + { name: "attr3", setAt: 1611141535729, value: 3 }, + ], + }, + sdk, + context, + undefined + ); + }); + }); + + describe("refresh()", () => { + it("should call client and load new data", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + + (provider.getContextData as ReturnType).mockReturnValue(Promise.resolve(refreshContextResponse)); + + await context.refresh(); + expect(provider.getContextData).toHaveBeenCalledTimes(1); + expect(provider.getContextData).toHaveBeenCalledWith(sdk, undefined); + + expect(context.experiments()).toEqual(refreshContextResponse.experiments.map((x) => x.name)); + for (const experiment of refreshContextResponse.experiments) { + expect(context.treatment(experiment.name)).toEqual(expectedVariants[experiment.name]); + } + expect(context.data()).toEqual(refreshContextResponse); + }); + + it("should pass through request options", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + + (provider.getContextData as ReturnType).mockReturnValue(Promise.resolve(refreshContextResponse)); + + await context.refresh({ timeout: 1234 } as any); + expect(provider.getContextData).toHaveBeenCalledTimes(1); + expect(provider.getContextData).toHaveBeenCalledWith(sdk, { timeout: 1234 }); + + expect(context.experiments()).toEqual(refreshContextResponse.experiments.map((x) => x.name)); + for (const experiment of refreshContextResponse.experiments) { + expect(context.treatment(experiment.name)).toEqual(expectedVariants[experiment.name]); + } + expect(context.data()).toEqual(refreshContextResponse); + }); + + it("should reject promise on error", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + + (provider.getContextData as ReturnType).mockReturnValueOnce(Promise.reject(new Error("test error"))); + + await expect(context.refresh()).rejects.toThrow("test error"); + }); + + it("should not re-queue exposures after refresh when not changed", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + + for (const experiment of getContextResponse.experiments) { + context.treatment(experiment.name); + } + + expect(context.pending()).toEqual(getContextResponse.experiments.length); + + (provider.getContextData as ReturnType).mockReturnValue(Promise.resolve(refreshContextResponse)); + + await context.refresh(); + expect(context.pending()).toEqual(getContextResponse.experiments.length); + + expect(provider.getContextData).toHaveBeenCalledTimes(1); + expect(provider.getContextData).toHaveBeenCalledWith(sdk, undefined); + + for (const experiment of getContextResponse.experiments) { + context.treatment(experiment.name); + } + + expect(context.pending()).toEqual(getContextResponse.experiments.length); + + for (const experiment of refreshContextResponse.experiments) { + context.treatment(experiment.name); + } + + expect(context.pending()).toEqual(refreshContextResponse.experiments.length); + }); + + it("should not re-queue when not changed on audience mismatch", async () => { + const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse as any); + + expect(context.treatment("exp_test_ab")).toEqual(0); + expect(context.pending()).toEqual(1); + + (provider.getContextData as ReturnType).mockReturnValue(Promise.resolve(audienceStrictContextResponse)); + + await context.refresh(); + expect(context.treatment("exp_test_ab")).toEqual(0); + expect(context.pending()).toEqual(1); + }); + + it("should not re-queue when not changed with override", async () => { + const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse as any); + + context.override("exp_test_ab", 3); + expect(context.treatment("exp_test_ab")).toEqual(3); + expect(context.pending()).toEqual(1); + + (provider.getContextData as ReturnType).mockReturnValue(Promise.resolve(audienceStrictContextResponse)); + + await context.refresh(); + expect(context.treatment("exp_test_ab")).toEqual(3); + expect(context.pending()).toEqual(1); + }); + + it("should not call client publish when failed", async () => { + const context = new Context(sdk, contextOptions, contextParams, Promise.reject("bad request error text") as any); + + await context.ready(); + await context.refresh(); + expect(provider.getContextData).not.toHaveBeenCalled(); + }); + + it("should call event logger when failed", async () => { + const context = new Context(sdk, contextOptions, contextParams, Promise.resolve(getContextResponse as any)); + await context.ready(); + (provider.getContextData as ReturnType).mockReturnValueOnce(Promise.reject(new Error("test error"))); + + defaultEventLogger.mockClear(); + await expect(context.refresh()).rejects.toThrow("test error"); + expect(defaultEventLogger).toHaveBeenCalledTimes(1); + expect(defaultEventLogger).toHaveBeenCalledWith(context, "error", expect.any(Error)); + }); + + it("should call event logger on success", async () => { + const context = new Context(sdk, contextOptions, contextParams, Promise.resolve(getContextResponse as any)); + + (provider.getContextData as ReturnType).mockReturnValueOnce(Promise.resolve(refreshContextResponse)); + + await context.ready(); + defaultEventLogger.mockClear(); + await context.refresh(); + expect(defaultEventLogger).toHaveBeenCalledTimes(1); + expect(defaultEventLogger).toHaveBeenCalledWith(context, "refresh", refreshContextResponse); + }); + + it("should throw after finalized() call", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + context.treatment("exp_test_ab"); + + expect(context.pending()).toEqual(1); + + const finalizePromise = context.finalize(); + + expect(context.isFinalizing()).toEqual(true); + expect(() => context.refresh()).toThrow(); + + await finalizePromise; + + expect(() => context.refresh()).toThrow(); + }); + + it("should keep overrides", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + + (provider.getContextData as ReturnType).mockReturnValue(Promise.resolve(refreshContextResponse)); + + context.override("not_found", 3); + expect(context.peek("not_found")).toEqual(3); + + await context.refresh(); + expect(context.peek("not_found")).toEqual(3); + }); + + it("should keep custom assignments", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + + (provider.getContextData as ReturnType).mockReturnValue(Promise.resolve(refreshContextResponse)); + + context.customAssignment("exp_test_ab", 3); + expect(context.peek("exp_test_ab")).toEqual(3); + + await context.refresh(); + expect(context.peek("exp_test_ab")).toEqual(3); + }); + + it("should pick up changes in experiment stopped", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + const experimentName = "exp_test_abc"; + + expect(context.treatment(experimentName)).toEqual(expectedVariants[experimentName]); + expect(context.pending()).toEqual(1); + + const stoppedRefreshContextResponse = clone(getContextResponse); + stoppedRefreshContextResponse.experiments = stoppedRefreshContextResponse.experiments.filter( + (x) => x.name !== experimentName + ); + + (provider.getContextData as ReturnType).mockReturnValue(Promise.resolve(stoppedRefreshContextResponse)); + + await context.refresh(); + expect(context.treatment(experimentName)).toEqual(0); + expect(context.pending()).toEqual(2); + }); + + it("should pick up changes in experiment started", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + const experimentName = "exp_test_new"; + + expect(context.treatment(experimentName)).toEqual(0); + expect(context.pending()).toEqual(1); + + (provider.getContextData as ReturnType).mockReturnValue(Promise.resolve(refreshContextResponse)); + + await context.refresh(); + expect(context.treatment(experimentName)).toEqual(1); + expect(context.pending()).toEqual(2); + }); + + it("should pick up changes in experiment fullon", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + const experimentName = "exp_test_abc"; + + expect(context.treatment(experimentName)).toEqual(expectedVariants[experimentName]); + expect(context.pending()).toEqual(1); + + const fullOnRefreshContextResponse = clone(getContextResponse); + for (const experiment of fullOnRefreshContextResponse.experiments) { + if (experiment.name === experimentName) { + expect(experiment.fullOnVariant).toEqual(0); + experiment.fullOnVariant = 1; + expect(expectedVariants[experimentName]).not.toEqual(experiment.fullOnVariant); + } + } + + (provider.getContextData as ReturnType).mockReturnValue(Promise.resolve(fullOnRefreshContextResponse)); + + await context.refresh(); + expect(context.treatment(experimentName)).toEqual(1); + expect(context.pending()).toEqual(2); + }); + + it("should pick up changes in experiment traffic split", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + const experimentName = "exp_test_not_eligible"; + + expect(context.treatment(experimentName)).toEqual(expectedVariants[experimentName]); + expect(context.pending()).toEqual(1); + + const scaledUpRefreshContextResponse = clone(getContextResponse); + for (const experiment of scaledUpRefreshContextResponse.experiments) { + if (experiment.name === experimentName) { + experiment.trafficSplit = [0.0, 1.0]; + } + } + + (provider.getContextData as ReturnType).mockReturnValue(Promise.resolve(scaledUpRefreshContextResponse)); + + await context.refresh(); + expect(context.treatment(experimentName)).toEqual(2); + expect(context.pending()).toEqual(2); + }); + + it("should pick up changes in experiment iteration", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + const experimentName = "exp_test_abc"; + + expect(context.treatment(experimentName)).toEqual(expectedVariants[experimentName]); + expect(context.pending()).toEqual(1); + + const iteratedRefreshContextResponse = clone(getContextResponse); + for (const experiment of iteratedRefreshContextResponse.experiments) { + if (experiment.name === experimentName) { + experiment.iteration = 2; + experiment.trafficSeedHi = 398724581; + experiment.seedHi = 34737352; + } + } + + (provider.getContextData as ReturnType).mockReturnValue(Promise.resolve(iteratedRefreshContextResponse)); + + await context.refresh(); + expect(context.treatment(experimentName)).toEqual(1); + expect(context.pending()).toEqual(2); + }); + + it("should pick up changes in experiment id", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + const experimentName = "exp_test_abc"; + + expect(context.treatment(experimentName)).toEqual(expectedVariants[experimentName]); + expect(context.pending()).toEqual(1); + + const iteratedRefreshContextResponse = clone(getContextResponse); + for (const experiment of iteratedRefreshContextResponse.experiments) { + if (experiment.name === experimentName) { + experiment.id = 11; + experiment.trafficSeedHi = 398724581; + experiment.seedHi = 34737352; + } + } + + (provider.getContextData as ReturnType).mockReturnValue(Promise.resolve(iteratedRefreshContextResponse)); + + await context.refresh(); + expect(context.treatment(experimentName)).toEqual(1); + expect(context.pending()).toEqual(2); + }); + }); + + describe("peek()", () => { + it("should not queue exposures", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + + for (const experiment of getContextResponse.experiments) { + expect(context.peek(experiment.name)).toEqual(expectedVariants[experiment.name]); + } + + expect(context.pending()).toEqual(0); + }); + + it("should return override variant", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + + for (const experiment of getContextResponse.experiments) { + context.override(experiment.name, expectedVariants[experiment.name] + 11); + } + context.override("not_found", 3); + + for (const experiment of getContextResponse.experiments) { + expect(context.peek(experiment.name)).toEqual(expectedVariants[experiment.name] + 11); + } + + expect(context.peek("not_found")).toEqual(3); + expect(context.pending()).toEqual(0); + }); + + it("should return assigned variant on audience mismatch in non-strict mode", () => { + const context = new Context(sdk, contextOptions, contextParams, audienceContextResponse as any); + expect(context.peek("exp_test_ab")).toEqual(1); + }); + + it("should return control variant on audience mismatch in strict mode", () => { + const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse as any); + expect(context.peek("exp_test_ab")).toEqual(0); + }); + + it("should re-evaluate audience expression when attributes change in strict mode", () => { + const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse as any); + + expect(context.peek("exp_test_ab")).toEqual(0); + + context.attribute("age", 25); + + expect(context.peek("exp_test_ab")).toEqual(1); + expect(context.pending()).toEqual(0); + }); + + it("should re-evaluate audience expression when attributes change in non-strict mode", () => { + const context = new Context(sdk, contextOptions, contextParams, audienceContextResponse as any); + + expect(context.peek("exp_test_ab")).toEqual(1); + + context.attribute("age", 25); + + expect(context.peek("exp_test_ab")).toEqual(1); + expect(context.pending()).toEqual(0); + }); + + it("should not re-evaluate audience when no new attributes set", () => { + const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse as any); + + context.attribute("age", 15); + + expect(context.peek("exp_test_ab")).toEqual(0); + expect(context.peek("exp_test_ab")).toEqual(0); + expect(context.pending()).toEqual(0); + }); + }); + + describe("treatment()", () => { + it("should queue exposures", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + + for (const experiment of getContextResponse.experiments) { + context.treatment(experiment.name); + } + + expect(context.pending()).toEqual(getContextResponse.experiments.length); + + for (const experiment of getContextResponse.experiments) { + context.treatment(experiment.name); + } + + expect(context.pending()).toEqual(getContextResponse.experiments.length); + + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + await context.publish(); + expect(publisher.publish).toHaveBeenCalledWith( + { + publishedAt: 1611141535729, + units: publishUnits, + hashed: true, + sdkVersion: SDK_VERSION, + exposures: [ + { + id: 1, + assigned: true, + eligible: true, + exposedAt: 1611141535729, + name: "exp_test_ab", + overridden: false, + unit: "session_id", + variant: 1, + fullOn: false, + custom: false, + audienceMismatch: false, + }, + { + id: 2, + assigned: true, + eligible: true, + exposedAt: 1611141535729, + name: "exp_test_abc", + overridden: false, + unit: "session_id", + variant: 2, + fullOn: false, + custom: false, + audienceMismatch: false, + }, + { + id: 3, + assigned: true, + eligible: false, + exposedAt: 1611141535729, + name: "exp_test_not_eligible", + overridden: false, + unit: "user_id", + variant: 0, + fullOn: false, + custom: false, + audienceMismatch: false, + }, + { + id: 4, + assigned: true, + eligible: true, + exposedAt: 1611141535729, + name: "exp_test_fullon", + overridden: false, + unit: "session_id", + variant: 2, + fullOn: true, + custom: false, + audienceMismatch: false, + }, + { + id: 5, + assigned: true, + eligible: true, + exposedAt: 1611141535729, + name: "exp_test_custom_fields", + overridden: false, + unit: "session_id", + variant: 1, + fullOn: false, + custom: false, + audienceMismatch: false, + }, + ], + }, + sdk, + context, + undefined + ); + }); + + it("should queue exposures only once", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + + for (const experiment of getContextResponse.experiments) { + context.treatment(experiment.name); + } + + expect(context.pending()).toEqual(getContextResponse.experiments.length); + + for (const experiment of getContextResponse.experiments) { + context.treatment(experiment.name); + } + + expect(context.pending()).toEqual(getContextResponse.experiments.length); + }); + + it("should call event logger", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + for (const experiment of getContextResponse.experiments) { + defaultEventLogger.mockClear(); + context.treatment(experiment.name); + expect(defaultEventLogger).toHaveBeenCalledTimes(1); + expect(defaultEventLogger).toHaveBeenCalledWith(context, "exposure", { + exposedAt: timeOrigin, + eligible: experiment.name !== "exp_test_not_eligible", + assigned: true, + overridden: false, + id: experiment.id, + name: experiment.name, + unit: experiment.unitType, + variant: expectedVariants[experiment.name], + fullOn: experiment.name === "exp_test_fullon", + custom: false, + audienceMismatch: false, + }); + } + + for (const experiment of getContextResponse.experiments) { + defaultEventLogger.mockClear(); + context.treatment(experiment.name); + expect(defaultEventLogger).not.toHaveBeenCalled(); + } + }); + + it("should queue exposure with base variant on unknown/stopped experiment", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + + expect(context.treatment("not_found")).toEqual(0); + expect(context.pending()).toEqual(1); + + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + await context.publish(); + expect(publisher.publish).toHaveBeenCalledWith( + { + publishedAt: 1611141535729, + units: publishUnits, + hashed: true, + sdkVersion: SDK_VERSION, + exposures: [ + { + id: 0, + assigned: false, + eligible: true, + exposedAt: 1611141535729, + name: "not_found", + overridden: false, + unit: null, + variant: 0, + fullOn: false, + custom: false, + audienceMismatch: false, + }, + ], + }, + sdk, + context, + undefined + ); + }); + + it("should queue exposure with audienceMatch true on audience match", async () => { + const context = new Context(sdk, contextOptions, contextParams, audienceContextResponse as any); + context.attribute("age", 21); + + expect(context.treatment("exp_test_ab")).toEqual(1); + expect(context.pending()).toEqual(1); + + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + await context.publish(); + expect(publisher.publish).toHaveBeenCalledWith( + { + publishedAt: 1611141535729, + units: publishUnits, + hashed: true, + sdkVersion: SDK_VERSION, + attributes: [{ name: "age", setAt: 1611141535729, value: 21 }], + exposures: [ + { + id: 1, + assigned: true, + eligible: true, + exposedAt: 1611141535729, + name: "exp_test_ab", + overridden: false, + unit: "session_id", + variant: 1, + fullOn: false, + custom: false, + audienceMismatch: false, + }, + ], + }, + sdk, + context, + undefined + ); + }); + + it("should queue exposure with audienceMatch false on audience mismatch", async () => { + const context = new Context(sdk, contextOptions, contextParams, audienceContextResponse as any); + + expect(context.treatment("exp_test_ab")).toEqual(1); + expect(context.pending()).toEqual(1); + + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + await context.publish(); + expect(publisher.publish).toHaveBeenCalledWith( + { + publishedAt: 1611141535729, + units: publishUnits, + hashed: true, + sdkVersion: SDK_VERSION, + exposures: [ + { + id: 1, + assigned: true, + eligible: true, + exposedAt: 1611141535729, + name: "exp_test_ab", + overridden: false, + unit: "session_id", + variant: 1, + fullOn: false, + custom: false, + audienceMismatch: true, + }, + ], + }, + sdk, + context, + undefined + ); + }); + + it("should queue exposure with audienceMatch false and control variant on audience mismatch in strict mode", async () => { + const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse as any); + + expect(context.treatment("exp_test_ab")).toEqual(0); + expect(context.pending()).toEqual(1); + + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + await context.publish(); + expect(publisher.publish).toHaveBeenCalledWith( + { + publishedAt: 1611141535729, + units: publishUnits, + hashed: true, + sdkVersion: SDK_VERSION, + exposures: [ + { + id: 1, + assigned: false, + eligible: true, + exposedAt: 1611141535729, + name: "exp_test_ab", + overridden: false, + unit: "session_id", + variant: 0, + fullOn: false, + custom: false, + audienceMismatch: true, + }, + ], + }, + sdk, + context, + undefined + ); + }); + + it("should not re-queue exposure on unknown experiment", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + + expect(context.pending()).toEqual(0); + expect(context.treatment("not_found")).toEqual(0); + expect(context.pending()).toEqual(1); + expect(context.treatment("not_found")).toEqual(0); + expect(context.pending()).toEqual(1); + }); + + it("should queue exposure with override variant", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + + context.override("exp_test_ab", 5); + context.override("not_found", 3); + + expect(context.treatment("exp_test_ab")).toEqual(5); + expect(context.treatment("not_found")).toEqual(3); + + expect(context.pending()).toEqual(2); + + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + await context.publish(); + expect(publisher.publish).toHaveBeenCalledWith( + { + publishedAt: 1611141535729, + units: publishUnits, + hashed: true, + sdkVersion: SDK_VERSION, + exposures: [ + { + id: 1, + assigned: false, + eligible: true, + exposedAt: 1611141535729, + name: "exp_test_ab", + overridden: true, + unit: "session_id", + variant: 5, + fullOn: false, + custom: false, + audienceMismatch: false, + }, + { + id: 0, + assigned: false, + eligible: true, + exposedAt: 1611141535729, + name: "not_found", + overridden: true, + unit: null, + variant: 3, + fullOn: false, + custom: false, + audienceMismatch: false, + }, + ], + }, + sdk, + context, + undefined + ); + }); + + it("should throw after finalized() call", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + context.treatment("exp_test_ab"); + + expect(context.pending()).toEqual(1); + + const finalizePromise = context.finalize(); + + expect(context.isFinalizing()).toEqual(true); + expect(() => context.treatment("exp_test_ab")).toThrow(); + + await finalizePromise; + + expect(() => context.treatment("exp_test_ab")).toThrow(); + }); + + it("should re-evaluate audience expression when attributes change in strict mode", () => { + const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse as any); + + expect(context.treatment("exp_test_ab")).toEqual(0); + expect(context.pending()).toEqual(1); + + context.attribute("age", 25); + + expect(context.treatment("exp_test_ab")).toEqual(1); + expect(context.pending()).toEqual(2); + }); + + it("should re-evaluate audience expression when attributes change in non-strict mode", () => { + const context = new Context(sdk, contextOptions, contextParams, audienceContextResponse as any); + + expect(context.treatment("exp_test_ab")).toEqual(1); + expect(context.pending()).toEqual(1); + + context.attribute("age", 25); + + expect(context.treatment("exp_test_ab")).toEqual(1); + expect(context.pending()).toEqual(2); + }); + + it("should not re-evaluate audience when no new attributes set", () => { + const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse as any); + + context.attribute("age", 15); + + expect(context.treatment("exp_test_ab")).toEqual(0); + expect(context.pending()).toEqual(1); + + expect(context.treatment("exp_test_ab")).toEqual(0); + expect(context.pending()).toEqual(1); + }); + + it("should not invalidate cache when audience result unchanged after attribute change", () => { + const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse as any); + + context.attribute("age", 15); + + expect(context.treatment("exp_test_ab")).toEqual(0); + expect(context.pending()).toEqual(1); + + context.attribute("age", 18); + + expect(context.treatment("exp_test_ab")).toEqual(0); + expect(context.pending()).toEqual(1); + }); + }); + + describe("variableValue()", () => { + it("should not return variable values when unassigned", () => { + const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse as any); + expect(context.pending()).toEqual(0); + expect(context.variableValue("banner.size", "17")).toEqual("17"); + }); + + it("should return variable values when overridden", () => { + const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse as any); + expect(context.pending()).toEqual(0); + context.override("exp_test_ab", 0); + expect(context.variableValue("banner.size", "17")).toEqual("tiny"); + }); + + it("conflicting key disjoint audiences", () => { + const context1 = new Context(sdk, contextOptions, contextParams, disjointedContextResponse as any); + const context2 = new Context(sdk, contextOptions, contextParams, disjointedContextResponse as any); + + expect(context1.pending()).toEqual(0); + expect(context2.pending()).toEqual(0); + + expect(expectedVariants["exp_test_ab"]).not.toEqual(0); + expect(expectedVariants["exp_test_abc"]).not.toEqual(0); + + context1.attribute("age", 20); + expect(context1.variableValue("icon", "square")).toEqual("arrow"); + + context2.attribute("age", 19); + expect(context2.variableValue("icon", "square")).toEqual("circle"); + }); + + it("should queue exposures", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + + const experiments = context.experiments()!; + + for (const [key, experimentNames] of Object.entries(variableExperiments)) { + const experimentName = experimentNames[0]; + const actual = context.variableValue(key, "17"); + const eligible = experimentName !== "exp_test_not_eligible"; + + if (eligible && experiments.indexOf(experimentName) !== -1) { + expect(actual).toEqual(expectedVariables[key]); + } else { + expect(actual).toBe("17"); + } + } + + expect(context.pending()).toEqual(getContextResponse.experiments.length); + + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + await context.publish(); + expect(publisher.publish).toHaveBeenCalledWith( + { + publishedAt: 1611141535729, + units: publishUnits, + hashed: true, + sdkVersion: SDK_VERSION, + exposures: [ + { + id: 1, + assigned: true, + eligible: true, + exposedAt: 1611141535729, + name: "exp_test_ab", + overridden: false, + unit: "session_id", + variant: 1, + fullOn: false, + custom: false, + audienceMismatch: false, + }, + { + id: 2, + assigned: true, + eligible: true, + exposedAt: 1611141535729, + name: "exp_test_abc", + overridden: false, + unit: "session_id", + variant: 2, + fullOn: false, + custom: false, + audienceMismatch: false, + }, + { + id: 3, + assigned: true, + eligible: false, + exposedAt: 1611141535729, + name: "exp_test_not_eligible", + overridden: false, + unit: "user_id", + variant: 0, + fullOn: false, + custom: false, + audienceMismatch: false, + }, + { + id: 4, + assigned: true, + eligible: true, + exposedAt: 1611141535729, + name: "exp_test_fullon", + overridden: false, + unit: "session_id", + variant: 2, + fullOn: true, + custom: false, + audienceMismatch: false, + }, + { + id: 5, + assigned: true, + eligible: true, + exposedAt: 1611141535729, + name: "exp_test_custom_fields", + overridden: false, + unit: "session_id", + variant: 1, + fullOn: false, + custom: false, + audienceMismatch: false, + }, + ], + }, + sdk, + context, + undefined + ); + }); + + it("should return defaultValue on unknown variable", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + expect(context.variableValue("not.found", "17")).toBe("17"); + }); + }); + + describe("peekVariableValue()", () => { + it("should not return variable values when unassigned", () => { + const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse as any); + expect(context.pending()).toEqual(0); + expect(context.peekVariableValue("banner.size", "17")).toEqual("17"); + }); + + it("should return variable values when overridden", () => { + const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse as any); + expect(context.pending()).toEqual(0); + context.override("exp_test_ab", 0); + expect(context.peekVariableValue("banner.size", "17")).toEqual("tiny"); + }); + + it("conflicting key disjoint audiences", () => { + const context1 = new Context(sdk, contextOptions, contextParams, disjointedContextResponse as any); + const context2 = new Context(sdk, contextOptions, contextParams, disjointedContextResponse as any); + + expect(context1.pending()).toEqual(0); + expect(context2.pending()).toEqual(0); + + context1.attribute("age", 20); + expect(context1.peekVariableValue("icon", "square")).toEqual("arrow"); + + context2.attribute("age", 19); + expect(context2.peekVariableValue("icon", "square")).toEqual("circle"); + + expect(context1.pending()).toEqual(0); + expect(context2.pending()).toEqual(0); + }); + + it("should pick lowest experiment id on conflicting key", () => { + const context = new Context(sdk, contextOptions, contextParams, lowestIdConflictingKeyContextResponse as any); + expect(context.pending()).toEqual(0); + + expect(expectedVariants["exp_test_ab"]).not.toEqual(0); + expect(expectedVariants["exp_test_abc"]).not.toEqual(0); + + expect(context.peekVariableValue("icon", "square")).toEqual("circle"); + }); + + it("should not queue exposures", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + + const experiments = context.experiments()!; + + for (const [key, experimentNames] of Object.entries(variableExperiments)) { + const experimentName = experimentNames[0]; + const actual = context.peekVariableValue(key, "17"); + const eligible = experimentName !== "exp_test_not_eligible"; + + if (eligible && experiments.indexOf(experimentName) !== -1) { + expect(actual).toEqual(expectedVariables[key]); + } else { + expect(actual).toBe("17"); + } + } + + expect(context.pending()).toEqual(0); + }); + }); + + describe("variableKeys()", () => { + it("should return all active keys", () => { + const context = new Context(sdk, contextOptions, contextParams, refreshContextResponse as any); + expect(context.pending()).toEqual(0); + + expect(context.variableKeys()).toMatchObject(variableExperiments); + expect(context.pending()).toEqual(0); + }); + }); + + describe("track()", () => { + it("should queue goals", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + + context.track("goal1", { amount: 125, hours: 245 }); + context.track("goal2", { tries: 7 }); + + expect(context.pending()).toEqual(2); + + context.track("goal2", { tests: 12 }); + + expect(context.pending()).toEqual(3); + + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + await context.publish(); + expect(publisher.publish).toHaveBeenCalledWith( + { + publishedAt: 1611141535729, + units: publishUnits, + hashed: true, + sdkVersion: SDK_VERSION, + goals: [ + { achievedAt: 1611141535729, name: "goal1", properties: { amount: 125, hours: 245 } }, + { achievedAt: 1611141535729, name: "goal2", properties: { tries: 7 } }, + { achievedAt: 1611141535729, name: "goal2", properties: { tests: 12 } }, + ], + }, + sdk, + context, + undefined + ); + }); + + it("should call event logger", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + + defaultEventLogger.mockClear(); + context.track("goal1", { amount: 125, hours: 245 }); + expect(defaultEventLogger).toHaveBeenCalledTimes(1); + expect(defaultEventLogger).toHaveBeenCalledWith(context, "goal", { + achievedAt: timeOrigin, + name: "goal1", + properties: { amount: 125, hours: 245 }, + }); + }); + + it("should not throw when goal property values are numbers or objects with number values", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + + expect(() => context.track("goal1", { test: { flt: 1.5, int: 2 } })).not.toThrowError(); + expect(() => context.track("goal1", { test: {} })).not.toThrowError(); + expect(() => context.track("goal1", { test: null })).not.toThrowError(); + + expect(context.pending()).toEqual(3); + }); + + it("should not throw when goal properties is null or undefined", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + + expect(() => context.track("goal1")).not.toThrowError(); + expect(() => context.track("goal1", null as any)).not.toThrowError(); + expect(() => context.track("goal1", undefined)).not.toThrowError(); + + expect(context.pending()).toEqual(3); + }); + + it("should throw when goal properties not object", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + + expect(() => context.track("goal1", 125.0 as any)).toThrowError("Goal 'goal1' properties must be of type object."); + expect(() => context.track("goal1", true as any)).toThrowError("Goal 'goal1' properties must be of type object."); + expect(() => context.track("goal1", "testy" as any)).toThrowError("Goal 'goal1' properties must be of type object."); + expect(() => context.track("goal1", [] as any)).toThrowError("Goal 'goal1' properties must be of type object."); + }); + + it("should throw after finalized() call", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + context.treatment("exp_test_ab"); + + expect(context.pending()).toEqual(1); + + const finalizePromise = context.finalize(); + + expect(context.isFinalizing()).toEqual(true); + expect(() => context.track("payment", { amount: 125 })).toThrow(); + + await finalizePromise; + + expect(() => context.track("payment", { amount: 125 })).toThrow(); + }); + + it("should queue when not ready", async () => { + const context = new Context(sdk, contextOptions, contextParams, Promise.resolve(getContextResponse as any)); + expect(context.pending()).toEqual(0); + expect(context.isReady()).toEqual(false); + + context.track("goal1", { amount: 125 }); + + expect(context.pending()).toEqual(1); + expect(context.isReady()).toEqual(false); + + await context.ready(); + expect(context.pending()).toEqual(1); + }); + }); + + describe("publish()", () => { + it("should not call client publish when queue is empty", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + await context.publish(); + expect(publisher.publish).not.toHaveBeenCalled(); + }); + + it("should propagate client error message", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + + context.track("goal1", { amount: 125 }); + + (publisher.publish as ReturnType).mockReturnValue(Promise.reject("test")); + + await expect(context.publish()).rejects.toEqual("test"); + }); + + it("should call client publish", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + + context.treatment("exp_test_ab"); + context.treatment("exp_test_not_eligible"); + + vi.spyOn(Date, "now").mockImplementation(() => timeOrigin + 1); + context.track("goal1", { amount: 125, hours: 245 }); + + vi.spyOn(Date, "now").mockImplementation(() => timeOrigin + 2); + context.attribute("attr1", "value1"); + + vi.spyOn(Date, "now").mockImplementation(() => timeOrigin + 3); + context.attributes({ + attr2: "value2", + attr3: 3, + attr4: 5.0, + attr5: true, + attr6: [1, 2, 3, 4], + attr7: null, + attr8: [], + attr9: [null, 1, 2], + attr10: ["one", null, "two"], + attr11: [null, null], + }); + + expect(context.pending()).toEqual(3); + + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + vi.spyOn(Date, "now").mockImplementation(() => timeOrigin + 100); + + await context.publish(); + expect(publisher.publish).toHaveBeenCalledTimes(1); + expect(publisher.publish).toHaveBeenCalledWith( + { + publishedAt: 1611141535829, + units: publishUnits, + hashed: true, + sdkVersion: SDK_VERSION, + exposures: [ + { + id: 1, + name: "exp_test_ab", + unit: "session_id", + exposedAt: 1611141535729, + variant: 1, + assigned: true, + eligible: true, + overridden: false, + fullOn: false, + custom: false, + audienceMismatch: false, + }, + { + id: 3, + name: "exp_test_not_eligible", + unit: "user_id", + exposedAt: 1611141535729, + variant: 0, + assigned: true, + eligible: false, + overridden: false, + fullOn: false, + custom: false, + audienceMismatch: false, + }, + ], + goals: [ + { + name: "goal1", + achievedAt: 1611141535730, + properties: { amount: 125, hours: 245 }, + }, + ], + attributes: [ + { name: "attr1", setAt: 1611141535731, value: "value1" }, + { name: "attr2", setAt: 1611141535732, value: "value2" }, + { name: "attr3", setAt: 1611141535732, value: 3 }, + { name: "attr4", setAt: 1611141535732, value: 5.0 }, + { name: "attr5", setAt: 1611141535732, value: true }, + { name: "attr6", setAt: 1611141535732, value: [1, 2, 3, 4] }, + { name: "attr7", setAt: 1611141535732, value: null }, + { name: "attr8", setAt: 1611141535732, value: [] }, + { name: "attr9", setAt: 1611141535732, value: [null, 1, 2] }, + { name: "attr10", setAt: 1611141535732, value: ["one", null, "two"] }, + { name: "attr11", setAt: 1611141535732, value: [null, null] }, + ], + }, + sdk, + context, + undefined + ); + + expect(context.pending()).toEqual(0); + }); + + it("should pass through request options", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + + context.track("goal1", { amount: 125, hours: 245 }); + + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + vi.spyOn(Date, "now").mockImplementation(() => timeOrigin + 100); + + await context.publish({ timeout: 1234 } as any); + expect(publisher.publish).toHaveBeenCalledTimes(1); + expect(publisher.publish).toHaveBeenCalledWith( + { + publishedAt: 1611141535829, + units: publishUnits, + hashed: true, + sdkVersion: SDK_VERSION, + goals: [ + { + name: "goal1", + achievedAt: 1611141535729, + properties: { amount: 125, hours: 245 }, + }, + ], + }, + sdk, + context, + { timeout: 1234 } + ); + + expect(context.pending()).toEqual(0); + }); + + it("should call event logger on error", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + + context.track("goal1", { amount: 125, hours: 245 }); + + (publisher.publish as ReturnType).mockReturnValue(Promise.reject("test error")); + + defaultEventLogger.mockClear(); + await expect(context.publish()).rejects.toEqual("test error"); + expect(defaultEventLogger).toHaveBeenCalledTimes(1); + expect(defaultEventLogger).toHaveBeenCalledWith(context, "error", "test error"); + }); + + it("should call event logger on success", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + + context.track("goal1", { amount: 125, hours: 245 }); + + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + vi.spyOn(Date, "now").mockImplementation(() => timeOrigin + 100); + + defaultEventLogger.mockClear(); + await context.publish(); + expect(defaultEventLogger).toHaveBeenCalledTimes(1); + expect(defaultEventLogger).toHaveBeenCalledWith(context, "publish", { + publishedAt: 1611141535829, + units: publishUnits, + hashed: true, + sdkVersion: SDK_VERSION, + goals: [ + { + achievedAt: 1611141535729, + name: "goal1", + properties: { amount: 125, hours: 245 }, + }, + ], + }); + }); + + it("should not call client publish when failed", async () => { + const context = new Context(sdk, contextOptions, contextParams, Promise.reject("bad request error text") as any); + await context.ready(); + context.treatment("exp_test_ab"); + + vi.spyOn(Date, "now").mockImplementation(() => timeOrigin + 1); + context.track("goal1", { amount: 125, hours: 245 }); + + expect(context.pending()).toEqual(2); + + await context.publish(); + expect(publisher.publish).not.toHaveBeenCalled(); + expect(context.pending()).toEqual(0); + }); + + it("should reset internal queues and keep attributes overrides and custom assignments", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + + context.treatment("exp_test_ab"); + context.track("goal1", { amount: 125, hours: 245 }); + context.attribute("attr1", "value1"); + + context.override("not_found", 3); + expect(context.treatment("not_found")).toEqual(3); + + context.customAssignment("exp_test_abc", 3); + expect(context.treatment("exp_test_abc")).toEqual(3); + + expect(context.pending()).toEqual(4); + + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve({})); + + vi.spyOn(Date, "now").mockImplementation(() => timeOrigin + 100); + + await context.publish(); + expect(publisher.publish).toHaveBeenCalledTimes(1); + expect(publisher.publish).toHaveBeenCalledWith( + { + publishedAt: 1611141535829, + units: publishUnits, + hashed: true, + sdkVersion: SDK_VERSION, + exposures: [ + { + id: 1, + name: "exp_test_ab", + unit: "session_id", + exposedAt: 1611141535729, + variant: 1, + assigned: true, + eligible: true, + overridden: false, + fullOn: false, + custom: false, + audienceMismatch: false, + }, + { + id: 0, + name: "not_found", + unit: null, + exposedAt: 1611141535729, + variant: 3, + assigned: false, + eligible: true, + overridden: true, + fullOn: false, + custom: false, + audienceMismatch: false, + }, + { + id: 2, + name: "exp_test_abc", + unit: "session_id", + exposedAt: 1611141535729, + variant: 3, + assigned: true, + eligible: true, + overridden: false, + fullOn: false, + custom: true, + audienceMismatch: false, + }, + ], + goals: [ + { + name: "goal1", + achievedAt: 1611141535729, + properties: { amount: 125, hours: 245 }, + }, + ], + attributes: [ + { name: "attr1", setAt: 1611141535729, value: "value1" }, + ], + }, + sdk, + context, + undefined + ); + + expect(context.pending()).toEqual(0); + + (publisher.publish as ReturnType).mockClear(); + + context.track("goal2", { test: 999 }); + + await context.publish(); + expect(publisher.publish).toHaveBeenCalledTimes(1); + expect(publisher.publish).toHaveBeenCalledWith( + { + publishedAt: 1611141535829, + units: publishUnits, + hashed: true, + sdkVersion: SDK_VERSION, + goals: [ + { + name: "goal2", + achievedAt: 1611141535829, + properties: { test: 999 }, + }, + ], + attributes: [ + { name: "attr1", setAt: 1611141535729, value: "value1" }, + ], + }, + sdk, + context, + undefined + ); + + expect(context.pending()).toEqual(0); + + expect(context.treatment("exp_test_abc")).toEqual(3); + expect(context.treatment("not_found")).toEqual(3); + + expect(context.pending()).toEqual(0); + }); + + it("should throw after finalized() call", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + context.treatment("exp_test_ab"); + + expect(context.pending()).toEqual(1); + + const finalizePromise = context.finalize(); + + expect(context.isFinalizing()).toEqual(true); + expect(() => context.publish()).toThrow(); + + await finalizePromise; + + expect(() => context.publish()).toThrow(); + }); + }); + + describe("finalize()", () => { + it("should not call client publish when queue is empty", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + expect(context.isFinalizing()).toEqual(false); + + await context.finalize(); + expect(publisher.publish).not.toHaveBeenCalled(); + expect(context.isFinalizing()).toEqual(false); + expect(context.isFinalized()).toEqual(true); + }); + + it("should propagate client error message", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + + context.treatment("exp_test_ab"); + + (publisher.publish as ReturnType).mockReturnValue(Promise.reject("test")); + + const finalizePromise = context.finalize(); + + expect(context.isFinalizing()).toEqual(true); + expect(context.isFinalized()).toEqual(false); + + await expect(finalizePromise).rejects.toEqual("test"); + expect(context.isFinalizing()).toEqual(false); + expect(context.isFinalized()).toEqual(false); + }); + + it("should call client publish", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + + context.treatment("exp_test_ab"); + + expect(context.pending()).toEqual(1); + + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + vi.spyOn(Date, "now").mockImplementation(() => timeOrigin + 100); + + const finalizePromise = context.finalize(); + + expect(context.isFinalizing()).toEqual(true); + expect(context.isFinalized()).toEqual(false); + + await finalizePromise; + expect(publisher.publish).toHaveBeenCalledTimes(1); + expect(publisher.publish).toHaveBeenCalledWith( + { + publishedAt: 1611141535829, + units: publishUnits, + hashed: true, + sdkVersion: SDK_VERSION, + exposures: [ + { + id: 1, + name: "exp_test_ab", + unit: "session_id", + exposedAt: 1611141535729, + variant: 1, + assigned: true, + eligible: true, + overridden: false, + fullOn: false, + custom: false, + audienceMismatch: false, + }, + ], + }, + sdk, + context, + undefined + ); + + expect(context.pending()).toEqual(0); + expect(context.isFinalizing()).toEqual(false); + expect(context.isFinalized()).toEqual(true); + }); + + it("should pass through request options", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + + context.treatment("exp_test_ab"); + + expect(context.pending()).toEqual(1); + + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + vi.spyOn(Date, "now").mockImplementation(() => timeOrigin + 100); + + const finalizePromise = context.finalize({ timeout: 1234 } as any); + + expect(context.isFinalizing()).toEqual(true); + expect(context.isFinalized()).toEqual(false); + + await finalizePromise; + expect(publisher.publish).toHaveBeenCalledTimes(1); + expect(publisher.publish).toHaveBeenCalledWith( + { + publishedAt: 1611141535829, + units: publishUnits, + hashed: true, + sdkVersion: SDK_VERSION, + exposures: [ + { + id: 1, + name: "exp_test_ab", + unit: "session_id", + exposedAt: 1611141535729, + variant: 1, + assigned: true, + eligible: true, + overridden: false, + fullOn: false, + custom: false, + audienceMismatch: false, + }, + ], + }, + sdk, + context, + { timeout: 1234 } + ); + + expect(context.pending()).toEqual(0); + expect(context.isFinalizing()).toEqual(false); + expect(context.isFinalized()).toEqual(true); + }); + + it("should call event logger on error", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + + context.treatment("exp_test_ab"); + + (publisher.publish as ReturnType).mockReturnValue(Promise.reject("test error")); + + defaultEventLogger.mockClear(); + const finalizePromise = context.finalize(); + + expect(context.isFinalizing()).toEqual(true); + expect(context.isFinalized()).toEqual(false); + + await expect(finalizePromise).rejects.toEqual("test error"); + expect(defaultEventLogger).toHaveBeenCalledTimes(1); + expect(defaultEventLogger).toHaveBeenCalledWith(context, "error", "test error"); + expect(context.isFinalizing()).toEqual(false); + expect(context.isFinalized()).toEqual(false); + }); + + it("should call event logger on success", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + + context.treatment("exp_test_ab"); + + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + defaultEventLogger.mockClear(); + const finalizePromise = context.finalize(); + + expect(context.isFinalizing()).toEqual(true); + expect(context.isFinalized()).toEqual(false); + + await finalizePromise; + expect(defaultEventLogger).toHaveBeenCalledTimes(2); + expect(defaultEventLogger).toHaveBeenLastCalledWith(context, "finalize", undefined); + expect(context.isFinalizing()).toEqual(false); + expect(context.isFinalized()).toEqual(true); + }); + + it("should not call client publish when failed", async () => { + const context = new Context(sdk, contextOptions, contextParams, Promise.reject("bad request error text") as any); + await context.ready(); + context.treatment("exp_test_ab"); + + expect(context.pending()).toEqual(1); + + expect(context.isFinalizing()).toEqual(false); + + await context.finalize(); + expect(publisher.publish).not.toHaveBeenCalled(); + expect(context.pending()).toEqual(0); + expect(context.isFinalizing()).toEqual(false); + expect(context.isFinalized()).toEqual(true); + }); + + it("should return current promise when called twice", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + + context.treatment("exp_test_ab"); + + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + const firstPromise = context.finalize(); + const secondPromise = context.finalize(); + + expect(secondPromise).toBe(firstPromise); + + expect(context.isFinalizing()).toEqual(true); + expect(context.isFinalized()).toEqual(false); + + await secondPromise; + + expect(context.isFinalizing()).toEqual(false); + expect(context.isFinalized()).toEqual(true); + }); + + it("should return completed promise when already finalized", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + + context.treatment("exp_test_ab"); + + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + await context.finalize(); + expect(context.isFinalizing()).toEqual(false); + expect(context.isFinalized()).toEqual(true); + + await context.finalize(); + }); + }); + + describe("override()", () => { + it("should be callable before ready()", async () => { + const context = new Context(sdk, contextOptions, contextParams, Promise.resolve(getContextResponse as any)); + expect(context.isReady()).toEqual(false); + expect(context.isFailed()).toEqual(false); + expect(context.isFinalized()).toEqual(false); + + context.override("exp_test_ab", 1); + context.overrides({ + exp_test_ab: 2, + exp_test_abc: 2, + not_found: 3, + }); + + await context.ready(); + expect(context.isReady()).toEqual(true); + expect(context.data()).toStrictEqual(getContextResponse); + + context.treatment("exp_test_ab"); + context.treatment("exp_test_abc"); + + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + vi.spyOn(Date, "now").mockImplementation(() => timeOrigin + 100); + + await context.publish(); + expect(publisher.publish).toHaveBeenCalledTimes(1); + expect(publisher.publish).toHaveBeenCalledWith( + { + publishedAt: 1611141535829, + units: publishUnits, + hashed: true, + sdkVersion: SDK_VERSION, + exposures: [ + { + id: 1, + name: "exp_test_ab", + unit: "session_id", + exposedAt: 1611141535729, + variant: 2, + assigned: false, + eligible: true, + overridden: true, + fullOn: false, + custom: false, + audienceMismatch: false, + }, + { + id: 2, + name: "exp_test_abc", + unit: "session_id", + exposedAt: 1611141535729, + variant: 2, + assigned: false, + eligible: true, + overridden: true, + fullOn: false, + custom: false, + audienceMismatch: false, + }, + ], + }, + sdk, + context, + undefined + ); + }); + }); + + describe("customAssignment()", () => { + it("should override natural assignment and set custom flag", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + + context.customAssignment("exp_test_abc", 11); + + expect(context.pending()).toEqual(0); + + context.treatment("exp_test_abc"); + + expect(context.pending()).toEqual(1); + + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + await context.publish(); + expect(publisher.publish).toHaveBeenCalledWith( + { + publishedAt: 1611141535729, + units: publishUnits, + hashed: true, + sdkVersion: SDK_VERSION, + exposures: [ + { + id: 2, + assigned: true, + eligible: true, + exposedAt: 1611141535729, + name: "exp_test_abc", + overridden: false, + unit: "session_id", + variant: 11, + fullOn: false, + custom: true, + audienceMismatch: false, + }, + ], + }, + sdk, + context, + undefined + ); + }); + + it("should not override full-on or non-eligible assignment", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + + context.customAssignment("exp_test_not_eligible", 11); + context.customAssignment("exp_test_fullon", 11); + + expect(context.pending()).toEqual(0); + + context.treatment("exp_test_not_eligible"); + context.treatment("exp_test_fullon"); + + expect(context.pending()).toEqual(2); + + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + await context.publish(); + expect(publisher.publish).toHaveBeenCalledWith( + { + publishedAt: 1611141535729, + units: publishUnits, + hashed: true, + sdkVersion: SDK_VERSION, + exposures: [ + { + id: 3, + assigned: true, + eligible: false, + exposedAt: 1611141535729, + name: "exp_test_not_eligible", + overridden: false, + unit: "user_id", + variant: 0, + fullOn: false, + custom: false, + audienceMismatch: false, + }, + { + id: 4, + assigned: true, + eligible: true, + exposedAt: 1611141535729, + name: "exp_test_fullon", + overridden: false, + unit: "session_id", + variant: 2, + fullOn: true, + custom: false, + audienceMismatch: false, + }, + ], + }, + sdk, + context, + undefined + ); + }); + + it("should be callable before ready()", async () => { + const context = new Context(sdk, contextOptions, contextParams, Promise.resolve(getContextResponse as any)); + expect(context.isReady()).toEqual(false); + expect(context.isFailed()).toEqual(false); + expect(context.isFinalized()).toEqual(false); + + context.customAssignment("exp_test_ab", 1); + context.customAssignments({ + exp_test_ab: 2, + exp_test_abc: 2, + not_found: 3, + }); + + await context.ready(); + expect(context.isReady()).toEqual(true); + expect(context.data()).toStrictEqual(getContextResponse); + + context.treatment("exp_test_ab"); + context.treatment("exp_test_abc"); + + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + vi.spyOn(Date, "now").mockImplementation(() => timeOrigin + 100); + + await context.publish(); + expect(publisher.publish).toHaveBeenCalledTimes(1); + expect(publisher.publish).toHaveBeenCalledWith( + { + publishedAt: 1611141535829, + units: publishUnits, + hashed: true, + sdkVersion: SDK_VERSION, + exposures: [ + { + id: 1, + name: "exp_test_ab", + unit: "session_id", + exposedAt: 1611141535729, + variant: 2, + assigned: true, + eligible: true, + overridden: false, + fullOn: false, + custom: true, + audienceMismatch: false, + }, + { + id: 2, + name: "exp_test_abc", + unit: "session_id", + exposedAt: 1611141535729, + variant: 2, + assigned: true, + eligible: true, + overridden: false, + fullOn: false, + custom: true, + audienceMismatch: false, + }, + ], + }, + sdk, + context, + undefined + ); + }); + + it("should throw after finalized() call", async () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + context.treatment("exp_test_ab"); + + expect(context.pending()).toEqual(1); + + const finalizePromise = context.finalize(); + + expect(context.isFinalizing()).toEqual(true); + expect(() => context.customAssignment("exp_test_ab", 3)).toThrow(); + + await finalizePromise; + + expect(() => context.customAssignment("exp_test_ab", 3)).toThrow(); + }); + }); + + describe("customFieldKeys()", () => { + it("should return custom field keys", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + const keys = context.customFieldKeys(); + + expect(context.isReady()).toEqual(true); + expect(keys).toEqual([ + "country", + "json_object", + "json_array", + "json_number", + "json_string", + "json_boolean", + "json_null", + "json_invalid", + "languages", + "text_field", + "string_field", + "number_field", + "boolean_field", + "false_boolean_field", + "invalid_type_field", + ]); + }); + }); + + describe("customFieldValue()", () => { + it("should return custom field value", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + const value = context.customFieldValue("exp_test_custom_fields", "country"); + + expect(context.isReady()).toEqual(true); + expect(value).toEqual("US,PT,ES"); + }); + + it("should return parsed JSON fields", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + + expect(context.customFieldValue("exp_test_abc", "json_object")).toEqual({ 123: 1, 456: 0 }); + expect(context.customFieldValue("exp_test_abc", "json_array")).toEqual(["hello", "world"]); + expect(context.customFieldValue("exp_test_abc", "json_number")).toEqual(123); + expect(context.customFieldValue("exp_test_abc", "json_string")).toEqual("hello"); + expect(context.customFieldValue("exp_test_abc", "json_boolean")).toEqual(true); + expect(context.customFieldValue("exp_test_abc", "json_null")).toEqual(null); + }); + + it("should return string and text fields", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + + expect(context.customFieldValue("exp_test_custom_fields", "text_field")).toEqual("hello text"); + expect(context.customFieldValue("exp_test_custom_fields", "string_field")).toEqual("hello string"); + }); + + it("should return parsed number fields", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + + expect(context.customFieldValue("exp_test_custom_fields", "number_field")).toEqual(123); + }); + + it("should return parsed boolean fields", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + + expect(context.customFieldValue("exp_test_custom_fields", "boolean_field")).toEqual(true); + expect(context.customFieldValue("exp_test_custom_fields", "false_boolean_field")).toEqual(false); + }); + + it("should console an error when JSON cannot be parsed", () => { + const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + + expect(context.customFieldValue("exp_test_abc", "json_invalid")).toEqual(null); + expect(errorSpy).toHaveBeenCalledTimes(1); + expect(errorSpy).toHaveBeenCalledWith( + "Failed to parse JSON custom field value 'json_invalid' for experiment 'exp_test_abc'" + ); + errorSpy.mockRestore(); + }); + + it("should console an error when a field type is invalid", () => { + const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + + expect(context.customFieldValue("exp_test_custom_fields", "invalid_type_field")).toEqual(null); + expect(errorSpy).toHaveBeenCalledTimes(1); + expect(errorSpy).toHaveBeenCalledWith( + "Unknown custom field type 'invalid' for experiment 'exp_test_custom_fields' and key 'invalid_type_field' - you may need to upgrade to the latest SDK version" + ); + errorSpy.mockRestore(); + }); + }); + + describe("customFieldValueType()", () => { + it("should return custom field value type", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.pending()).toEqual(0); + const value = context.customFieldValueType("exp_test_custom_fields", "country"); + + expect(context.isReady()).toEqual(true); + expect(value).toEqual("string"); + }); + }); + + describe("includeSystemAttributes", () => { + it("should not include system attributes by default", async () => { + const defaultOptions = { publishDelay: -1, refreshPeriod: 0 }; + + const context = new Context(sdk, defaultOptions, contextParams, getContextResponse as any); + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + context.treatment("exp_test_ab"); + + await context.publish(); + const call = (publisher.publish as ReturnType).mock.calls[0]; + const request = call[0]; + + expect(request.attributes).toBeUndefined(); + }); + + it("should include system attributes when includeSystemAttributes is true", async () => { + const optionsWithSystemAttrs = { + publishDelay: -1, + refreshPeriod: 0, + includeSystemAttributes: true, + }; + + const context = new Context(sdk, optionsWithSystemAttrs, contextParams, getContextResponse as any); + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + context.treatment("exp_test_ab"); + + await context.publish(); + const call = (publisher.publish as ReturnType).mock.calls[0]; + const request = call[0]; + + expect(request.attributes).toBeDefined(); + expect(request.attributes.length).toBeGreaterThanOrEqual(4); + + const sdkNameAttr = request.attributes.find((a: any) => a.name === "sdk_name"); + const sdkVersionAttr = request.attributes.find((a: any) => a.name === "sdk_version"); + const applicationAttr = request.attributes.find((a: any) => a.name === "application"); + const environmentAttr = request.attributes.find((a: any) => a.name === "environment"); + + expect(sdkNameAttr).toBeDefined(); + expect(sdkNameAttr.value).toEqual("absmartly-javascript-sdk"); + expect(sdkNameAttr.setAt).toEqual(expect.any(Number)); + + expect(sdkVersionAttr).toBeDefined(); + expect(sdkVersionAttr.value).toEqual(SDK_VERSION); + expect(sdkVersionAttr.setAt).toEqual(expect.any(Number)); + + expect(applicationAttr).toBeDefined(); + expect(applicationAttr.value).toEqual("website"); + expect(applicationAttr.setAt).toEqual(expect.any(Number)); + + expect(environmentAttr).toBeDefined(); + expect(environmentAttr.value).toEqual("production"); + expect(environmentAttr.setAt).toEqual(expect.any(Number)); + }); + + it("should prepend system attributes before user attributes", async () => { + const optionsWithSystemAttrs = { + publishDelay: -1, + refreshPeriod: 0, + includeSystemAttributes: true, + }; + + const context = new Context(sdk, optionsWithSystemAttrs, contextParams, getContextResponse as any); + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + context.attribute("custom_attr", "custom_value"); + context.treatment("exp_test_ab"); + + await context.publish(); + const call = (publisher.publish as ReturnType).mock.calls[0]; + const request = call[0]; + + expect(request.attributes[0].name).toEqual("sdk_name"); + expect(request.attributes[1].name).toEqual("sdk_version"); + expect(request.attributes[2].name).toEqual("application"); + expect(request.attributes[3].name).toEqual("environment"); + expect(request.attributes[4].name).toEqual("custom_attr"); + expect(request.attributes[4].value).toEqual("custom_value"); + }); + + it("should include app_version when application version is set", async () => { + client.getApplication.mockReturnValueOnce({ name: "website", version: 3 }); + + const optionsWithSystemAttrs = { + publishDelay: -1, + refreshPeriod: 0, + includeSystemAttributes: true, + }; + + const context = new Context(sdk, optionsWithSystemAttrs, contextParams, getContextResponse as any); + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + context.treatment("exp_test_ab"); + + await context.publish(); + const call = (publisher.publish as ReturnType).mock.calls[0]; + const request = call[0]; + + const appVersionAttr = request.attributes.find((a: any) => a.name === "app_version"); + expect(appVersionAttr).toBeDefined(); + expect(appVersionAttr.value).toEqual(3); + }); + + it("should not include app_version when application version is 0", async () => { + const optionsWithSystemAttrs = { + publishDelay: -1, + refreshPeriod: 0, + includeSystemAttributes: true, + }; + + const context = new Context(sdk, optionsWithSystemAttrs, contextParams, getContextResponse as any); + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + context.treatment("exp_test_ab"); + + await context.publish(); + const call = (publisher.publish as ReturnType).mock.calls[0]; + const request = call[0]; + + const appVersionAttr = request.attributes.find((a: any) => a.name === "app_version"); + expect(appVersionAttr).toBeUndefined(); + }); + + it("should include app_version when application version is a semver string", async () => { + client.getApplication.mockReturnValueOnce({ name: "website", version: "1.2.3" }); + + const optionsWithSystemAttrs = { + publishDelay: -1, + refreshPeriod: 0, + includeSystemAttributes: true, + }; + + const context = new Context(sdk, optionsWithSystemAttrs, contextParams, getContextResponse as any); + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + context.treatment("exp_test_ab"); + + await context.publish(); + const call = (publisher.publish as ReturnType).mock.calls[0]; + const request = call[0]; + + const appVersionAttr = request.attributes.find((a: any) => a.name === "app_version"); + expect(appVersionAttr).toBeDefined(); + expect(appVersionAttr.value).toEqual("1.2.3"); + }); + + it("should only include user attributes when includeSystemAttributes is not set", async () => { + const defaultOptions = { publishDelay: -1, refreshPeriod: 0 }; + + const context = new Context(sdk, defaultOptions, contextParams, getContextResponse as any); + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + context.attribute("custom_attr", "custom_value"); + context.treatment("exp_test_ab"); + + await context.publish(); + const call = (publisher.publish as ReturnType).mock.calls[0]; + const request = call[0]; + + expect(request.attributes).toEqual([ + { name: "custom_attr", value: "custom_value", setAt: expect.any(Number) }, + ]); + }); + }); +}); diff --git a/src/context.ts b/src/context.ts index c862b3d..ad2557b 100644 --- a/src/context.ts +++ b/src/context.ts @@ -1,116 +1,41 @@ -import { arrayEqualsShallow, hashUnit, isObject, isPromise } from "./utils"; +import { hashUnit } from "./hashing"; import { VariantAssigner } from "./assigner"; import { AudienceMatcher } from "./matcher"; import { insertUniqueSorted } from "./algorithm"; -import SDK, { EventLogger, EventName } from "./sdk"; -import { ContextPublisher, PublishParams } from "./publisher"; -import { ContextDataProvider } from "./provider"; -import { ClientRequestOptions } from "./client"; +import { arrayEqualsShallow, isObject, isPromise } from "./utils"; import { SDK_VERSION } from "./version"; +import { ContextPublisher } from "./publisher"; +import { ContextDataProvider } from "./provider"; +import type { + Assignment, + Attribute, + ContextData, + ContextParams, + Experiment, + ExperimentData, + Exposure, + Goal, + Units, + PublishParams, + ClientRequestOptions, + EventLogger, + EventName, + JSONValue, + CustomFieldValue, +} from "./types"; + +interface SDKLike { + getClient(): { + getAgent(): string; + getApplication(): { name: string; version: number | string }; + getEnvironment(): string; + }; + getContextPublisher(): ContextPublisher; + getContextDataProvider(): ContextDataProvider; + getEventLogger(): EventLogger; +} -type JSONPrimitive = string | number | boolean | null; -type JSONObject = { [key: string]: JSONValue }; -type JSONArray = JSONValue[]; -type JSONValue = JSONPrimitive | JSONObject | JSONArray; - -type CustomFieldValueType = "text" | "string" | "number" | "json" | "boolean"; - -type CustomFieldValue = { - name: string; - value: string; - type: CustomFieldValueType; -}; - -export type ExperimentData = { - id: number; - name: string; - unitType: string | null; - iteration: number; - fullOnVariant: number; - trafficSplit: number[]; - trafficSeedHi: number; - trafficSeedLo: number; - audience: string; - audienceStrict: boolean; - split: number[]; - seedHi: number; - seedLo: number; - variants: { config: null | string }[]; - variables: Record; - variant: number; - overridden: boolean; - assigned: boolean; - exposed: boolean; - eligible: boolean; - fullOn: boolean; - custom: boolean; - audienceMismatch: boolean; - customFieldValues: CustomFieldValue[] | null; -}; - -type Assignment = { - id: number; - iteration: number; - fullOnVariant: number; - unitType: string | null; - variant: number; - overridden: boolean; - assigned: boolean; - exposed: boolean; - eligible: boolean; - fullOn: boolean; - custom: boolean; - audienceMismatch: boolean; - trafficSplit?: number[]; - variables?: Record; - attrsSeq?: number; -}; - -export type Experiment = { - data: ExperimentData; - variables: Record[]; -}; - -export type Unit = { - type: string; - uid: string | null; -}; - -export type Exposure = { - id: number; - name: string; - exposedAt: number; - unit: string | null; - variant: number; - assigned: boolean; - eligible: boolean; - overridden: boolean; - fullOn: boolean; - custom: boolean; - audienceMismatch: boolean; -}; - -export type Attribute = { - name: string; - value: unknown; - setAt: number; -}; - -export type Units = { - [key: string]: string | number; -}; - -export type Goal = { - name: string; - properties: Record | null; - achievedAt: number; -}; - -export type ContextParams = { - units: Record; -}; - -export type ContextOptions = { +type ContextOptionsInternal = { publisher?: ContextPublisher; dataProvider?: ContextDataProvider; eventLogger?: EventLogger; @@ -119,30 +44,26 @@ export type ContextOptions = { includeSystemAttributes?: boolean; }; -export type ContextData = { - experiments?: ExperimentData[]; -}; - -export default class Context { +export class Context { private readonly _assigners: Record; private readonly _attrs: Attribute[]; private readonly _audienceMatcher: AudienceMatcher; private readonly _cassignments: Record; private readonly _dataProvider: ContextDataProvider; private readonly _eventLogger: EventLogger; - private readonly _opts: ContextOptions; + private readonly _opts: ContextOptionsInternal; private readonly _publisher: ContextPublisher; - private readonly _sdk: SDK; + private readonly _sdk: SDKLike; private readonly _units: Units; private _assignments: Record; - private _data: ContextData; + private _data!: ContextData; private _exposures: Exposure[]; private _failed: boolean; private _finalized: boolean; private _finalizing: boolean | Promise | null; private _goals: Goal[]; - private _index: Record; - private _indexVariables: Record; + private _index!: Record; + private _indexVariables!: Record; private _overrides: Record; private _pending: number; private _attrsSeq: number; @@ -151,7 +72,7 @@ export default class Context { private _publishTimeout?: ReturnType; private _refreshInterval?: ReturnType; - constructor(sdk: SDK, options: ContextOptions, params: ContextParams, promise: ContextData | Promise) { + constructor(sdk: SDKLike, options: ContextOptionsInternal, params: ContextParams, promise: ContextData | Promise) { this._sdk = sdk; this._publisher = options.publisher || this._sdk.getContextPublisher(); this._dataProvider = options.dataProvider || this._sdk.getContextDataProvider(); @@ -195,10 +116,8 @@ export default class Context { this._logError(error); }); } else { - promise = promise as ContextData; - this._init(promise); - - this._logEvent("ready", promise); + this._init(promise as ContextData); + this._logEvent("ready", promise as ContextData); } } @@ -234,7 +153,6 @@ export default class Context { data() { this._checkReady(); - return this._data; } @@ -309,18 +227,16 @@ export default class Context { } units(units: Record) { - Object.entries(units).forEach(([unitType, uid]) => { + for (const [unitType, uid] of Object.entries(units)) { this.unit(unitType, uid); - }); + } } getAttribute(attrName: string) { let result; - - this._attrs.forEach((attr) => { + for (const attr of this._attrs) { if (attr.name === attrName) result = attr.value; - }); - + } return result; } @@ -333,35 +249,30 @@ export default class Context { getAttributes() { const attributes: Record = {}; - this._attrs - .map((a) => [a.name, a.value]) - .forEach(([key, value]) => { - attributes[key as string] = value; - }); + for (const a of this._attrs) { + attributes[a.name] = a.value; + } return attributes; } attributes(attrs: Record) { - Object.entries(attrs).forEach(([attrName, value]) => { + for (const [attrName, value] of Object.entries(attrs)) { this.attribute(attrName, value); - }); + } } peek(experimentName: string) { this._checkReady(true); - return this._peek(experimentName).variant; } treatment(experimentName: string) { this._checkReady(true); - return this._treatment(experimentName).variant; } track(goalName: string, properties?: Record) { this._checkNotFinalized(); - return this._track(goalName, properties); } @@ -371,19 +282,16 @@ export default class Context { experiments() { this._checkReady(); - return this._data.experiments?.map((x) => x.name); } variableValue(key: string, defaultValue: string): string { this._checkReady(true); - return this._variableValue(key, defaultValue); } peekVariableValue(key: string, defaultValue: string): string { this._checkReady(true); - return this._peekVariable(key, defaultValue); } @@ -392,12 +300,12 @@ export default class Context { const variableExperiments: Record = {}; - Object.entries(this._indexVariables).forEach(([key, values]) => { - values.forEach((value) => { + for (const [key, values] of Object.entries(this._indexVariables)) { + for (const value of values) { if (variableExperiments[key]) variableExperiments[key].push(value.data.name); else variableExperiments[key] = [value.data.name]; - }); - }); + } + } return variableExperiments; } @@ -407,21 +315,35 @@ export default class Context { } overrides(experimentVariants: Record) { - Object.entries(experimentVariants).forEach(([experimentName, variant]) => { + for (const [experimentName, variant] of Object.entries(experimentVariants)) { this.override(experimentName, variant); - }); + } } customAssignment(experimentName: string, variant: number) { this._checkNotFinalized(); - this._cassignments[experimentName] = variant; } customAssignments(experimentVariants: Record) { - Object.entries(experimentVariants).forEach(([experimentName, variant]) => { + for (const [experimentName, variant] of Object.entries(experimentVariants)) { this.customAssignment(experimentName, variant); - }); + } + } + + customFieldKeys() { + this._checkReady(true); + return this._customFieldKeys(); + } + + customFieldValue(experimentName: string, key: string) { + this._checkReady(true); + return this._customFieldValue(experimentName, key); + } + + customFieldValueType(experimentName: string, key: string) { + this._checkReady(true); + return this._customFieldValueType(experimentName, key); } private _checkNotFinalized() { @@ -444,9 +366,9 @@ export default class Context { private _getAttributesMap(): Record { const attrs: Record = {}; - this._attrs.forEach((attr) => { + for (const attr of this._attrs) { attrs[attr.name] = attr.value; - }); + } return attrs; } @@ -485,17 +407,14 @@ export default class Context { const assignment = this._assignments[experimentName]; if (hasOverride) { if (assignment.overridden && assignment.variant === this._overrides[experimentName]) { - // override up-to-date return assignment; } } else if (experiment == null) { if (!assignment.assigned) { - // previously not-running experiment return assignment; } } else if (!hasCustom || this._cassignments[experimentName] === assignment.variant) { if (experimentMatches(experiment.data, assignment) && audienceMatches(experiment.data, assignment)) { - // assignment up-to-date return assignment; } } @@ -583,7 +502,6 @@ export default class Context { assignment.fullOn = true; } - // store these so we can detect changes to running experiment assignment.unitType = unitType; assignment.id = experiment.data.id; assignment.iteration = experiment.data.iteration; @@ -609,7 +527,6 @@ export default class Context { if (!assignment.exposed) { assignment.exposed = true; - this._queueExposure(experimentName, assignment); } @@ -654,17 +571,11 @@ export default class Context { return Array.from(keys); } - customFieldKeys() { - this._checkReady(true); - - return this._customFieldKeys(); - } - private _customFieldValue(experimentName: string, key: string): JSONValue { const experiment = this._index[experimentName]; if (experiment != null) { - const field = experiment.data.customFieldValues?.find((x) => x.name === key); + const field = experiment.data.customFieldValues?.find((x: CustomFieldValue) => x.name === key); if (field != null) { switch (field.type) { case "text": @@ -677,7 +588,7 @@ export default class Context { if (field.value === "null") return null; if (field.value === "") return ""; return JSON.parse(field.value); - } catch (e) { + } catch (_e) { console.error(`Failed to parse JSON custom field value '${key}' for experiment '${experimentName}'`); return null; } @@ -695,17 +606,11 @@ export default class Context { return null; } - customFieldValue(experimentName: string, key: string) { - this._checkReady(true); - - return this._customFieldValue(experimentName, key); - } - private _customFieldValueType(experimentName: string, key: string) { const experiment = this._index[experimentName]; if (experiment != null) { - const field = experiment.data.customFieldValues?.find((x) => x.name === key); + const field = experiment.data.customFieldValues?.find((x: CustomFieldValue) => x.name === key); if (field != null) { return field.type; } @@ -714,12 +619,6 @@ export default class Context { return null; } - customFieldValueType(experimentName: string, key: string) { - this._checkReady(true); - - return this._customFieldValueType(experimentName, key); - } - private _variableValue(key: string, defaultValue: string): string { for (const i in this._indexVariables[key]) { const experimentName = this._indexVariables[key][i].data.name; @@ -727,7 +626,6 @@ export default class Context { if (assignment.variables !== undefined) { if (!assignment.exposed) { assignment.exposed = true; - this._queueExposure(experimentName, assignment); } @@ -926,9 +824,9 @@ export default class Context { } } - private _logEvent(eventName: EventName, data?: Record) { + private _logEvent(eventName: EventName, data?: unknown) { if (this._eventLogger) { - this._eventLogger(this, eventName, data); + this._eventLogger(this, eventName, data as any); } } @@ -971,7 +869,7 @@ export default class Context { const config = variant.config; const parsed = config != null && config.length > 0 ? JSON.parse(config) : {}; - Object.keys(parsed).forEach((key) => { + for (const key of Object.keys(parsed)) { const value = entry; if (indexVariables[key]) { insertUniqueSorted( @@ -980,7 +878,7 @@ export default class Context { (a, b) => (a as Experiment).data.id < (b as Experiment).data.id ); } else indexVariables[key] = [value]; - }); + } variables[i] = parsed; }); @@ -1013,7 +911,6 @@ export default class Context { } else { this._finalized = true; this._logEvent("finalize"); - resolve(); } }, requestOptions); From a84bd00fefe41625c5c13ecb22d97e3d3d78c896 Mon Sep 17 00:00:00 2001 From: Jonas Alves Date: Sun, 29 Mar 2026 00:25:37 +0000 Subject: [PATCH 14/22] feat: add SDK class with context creation and configuration --- src/__tests__/sdk.test.ts | 84 ++++++++++++++++++++++ src/sdk.ts | 148 ++++++++++++++++++++------------------ 2 files changed, 164 insertions(+), 68 deletions(-) create mode 100644 src/__tests__/sdk.test.ts diff --git a/src/__tests__/sdk.test.ts b/src/__tests__/sdk.test.ts new file mode 100644 index 0000000..d1cba1d --- /dev/null +++ b/src/__tests__/sdk.test.ts @@ -0,0 +1,84 @@ +import { describe, expect, test, vi } from "vitest"; +import { SDK } from "../sdk"; +import type { ClientOptions } from "../types"; + +const defaultOpts: ClientOptions = { + agent: "test-agent", + apiKey: "test-api-key", + application: "test-app", + endpoint: "https://test.absmartly.io/v1", + environment: "test", +}; + +describe("SDK", () => { + test("creates SDK instance", () => { + const sdk = new SDK(defaultOpts); + expect(sdk).toBeInstanceOf(SDK); + }); + + test("getClient returns client", () => { + const sdk = new SDK(defaultOpts); + expect(sdk.getClient()).toBeDefined(); + }); + + test("get/set event logger", () => { + const sdk = new SDK(defaultOpts); + const logger = vi.fn(); + sdk.setEventLogger(logger); + expect(sdk.getEventLogger()).toBe(logger); + }); + + test("default event logger logs errors", () => { + const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + const error = new Error("test"); + SDK.defaultEventLogger({} as never, "error", error); + expect(consoleSpy).toHaveBeenCalledWith(error); + consoleSpy.mockRestore(); + }); + + test("createContext validates unit types", () => { + const sdk = new SDK(defaultOpts); + expect(() => sdk.createContext({ units: { session_id: true as unknown as string } })).toThrow( + "Unit 'session_id' UID is of unsupported type 'boolean'. UID must be one of ['string', 'number']", + ); + }); + + test("createContext validates empty string units", () => { + const sdk = new SDK(defaultOpts); + expect(() => sdk.createContext({ units: { session_id: "" } })).toThrow( + "Unit 'session_id' UID length must be >= 1", + ); + }); + + test("createContext returns Context instance", () => { + vi.stubGlobal( + "fetch", + vi.fn().mockResolvedValue({ ok: true, json: () => Promise.resolve({ experiments: [] }) }), + ); + const sdk = new SDK(defaultOpts); + const context = sdk.createContext({ units: { session_id: "abc" } }); + expect(context).toBeDefined(); + vi.restoreAllMocks(); + }); + + test("createContextWith accepts pre-fetched data", () => { + const sdk = new SDK(defaultOpts); + const context = sdk.createContextWith({ units: { session_id: "abc" } }, { experiments: [] }); + expect(context).toBeDefined(); + expect(context.isReady()).toBe(true); + }); + + test("get/set context publisher", () => { + const sdk = new SDK(defaultOpts); + const publisher = { publish: vi.fn() }; + sdk.setContextPublisher(publisher as never); + expect(sdk.getContextPublisher()).toBe(publisher); + }); + + test("get/set context data provider", () => { + const sdk = new SDK(defaultOpts); + const provider = { getContextData: vi.fn() }; + sdk.setContextDataProvider(provider as never); + expect(sdk.getContextDataProvider()).toBe(provider); + }); +}); diff --git a/src/sdk.ts b/src/sdk.ts index 32ac4cf..6a6a631 100644 --- a/src/sdk.ts +++ b/src/sdk.ts @@ -1,14 +1,27 @@ -import Client, { ClientOptions, ClientRequestOptions } from "./client"; -import Context, { ContextData, ContextOptions, ContextParams, Exposure, Goal } from "./context"; -import { ContextPublisher, PublishParams } from "./publisher"; +import { Client } from "./client"; +import { Context } from "./context"; +import { ContextPublisher } from "./publisher"; import { ContextDataProvider } from "./provider"; -import { isLongLivedApp } from "./utils"; - -export type EventLoggerData = Error | Exposure | Goal | ContextData | PublishParams; - -export type EventName = "error" | "ready" | "refresh" | "publish" | "exposure" | "goal" | "finalize"; +import type { + ClientOptions, + ClientRequestOptions, + ContextData, + ContextParams, + EventLogger, + EventLoggerData, +} from "./types"; + +type ContextOptionsInput = { + publisher?: ContextPublisher; + dataProvider?: ContextDataProvider; + eventLogger?: EventLogger; + refreshPeriod?: number; + publishDelay?: number; + includeSystemAttributes?: boolean; +}; -export type EventLogger = (context: Context, eventName: EventName, data?: EventLoggerData) => void; +type ContextOptionsFull = Required> & + Omit; export type SDKOptions = { client?: Client; @@ -17,12 +30,29 @@ export type SDKOptions = { provider?: ContextDataProvider; }; -export default class SDK { - static defaultEventLogger: EventLogger = (_, eventName, data) => { - if (eventName === "error") { - console.error(data); - } +function isLongLivedApp(): boolean { + return ( + (typeof window !== "undefined" && typeof window.document !== "undefined") || + (typeof navigator !== "undefined" && navigator.product === "ReactNative") + ); +} + +const CLIENT_OPTION_KEYS = [ + "application", + "agent", + "apiKey", + "endpoint", + "keepalive", + "environment", + "retries", + "timeout", +]; + +export class SDK { + static defaultEventLogger: EventLogger = (_: unknown, eventName: string, data?: EventLoggerData) => { + if (eventName === "error") console.error(data); }; + private _eventLogger: EventLogger; private _publisher: ContextPublisher; private _provider: ContextDataProvider; @@ -30,20 +60,11 @@ export default class SDK { constructor(options: ClientOptions & SDKOptions) { const clientOptions = Object.assign( - { - agent: "absmartly-javascript-sdk", - }, + { agent: "absmartly-javascript-sdk" }, ...Object.entries(options || {}) - .filter( - (x) => - ["application", "agent", "apiKey", "endpoint", "keepalive", "environment", "retries", "timeout"].indexOf( - x[0] - ) !== -1 - ) - .map((x) => ({ [x[0]]: x[1] })) - ); - - options = Object.assign({}, options); + .filter((x) => CLIENT_OPTION_KEYS.indexOf(x[0]) !== -1) + .map((x) => ({ [x[0]]: x[1] })), + ) as ClientOptions; this._client = options.client || new Client(clientOptions); this._eventLogger = options.eventLogger || SDK.defaultEventLogger; @@ -51,86 +72,77 @@ export default class SDK { this._provider = options.provider || new ContextDataProvider(); } - getContextData(requestOptions: ClientRequestOptions) { + getContextData(requestOptions: ClientRequestOptions): Promise { return this._provider.getContextData(this, requestOptions); } createContext( params: ContextParams, - options?: Partial, - requestOptions?: Partial - ) { + options?: Partial, + requestOptions?: Partial, + ): Context { SDK._validateParams(params); - const fullOptions = SDK._contextOptions(options); const data = this._provider.getContextData(this, requestOptions); return new Context(this, fullOptions, params, data); } - setEventLogger(logger: EventLogger) { + createContextWith( + params: ContextParams, + data: ContextData | Promise, + options?: Partial, + ): Context { + SDK._validateParams(params); + const fullOptions = SDK._contextOptions(options); + return new Context(this, fullOptions, params, data); + } + + setEventLogger(logger: EventLogger): void { this._eventLogger = logger; } - getEventLogger() { + getEventLogger(): EventLogger { return this._eventLogger; } - setContextPublisher(publisher: ContextPublisher) { + setContextPublisher(publisher: ContextPublisher): void { this._publisher = publisher; } - getContextPublisher() { + getContextPublisher(): ContextPublisher { return this._publisher; } - setContextDataProvider(provider: ContextDataProvider) { + setContextDataProvider(provider: ContextDataProvider): void { this._provider = provider; } - getContextDataProvider() { + getContextDataProvider(): ContextDataProvider { return this._provider; } - getClient() { + getClient(): Client { return this._client; } - createContextWith( - params: ContextParams, - data: ContextData | Promise, - options?: Partial - ) { - SDK._validateParams(params); - - const fullOptions = SDK._contextOptions(options); - - return new Context(this, fullOptions, params, data); - } - - private static _contextOptions(options?: Partial): ContextOptions { + private static _contextOptions(options?: Partial): ContextOptionsFull { return Object.assign( - { - publishDelay: isLongLivedApp() ? 100 : -1, - refreshPeriod: 0, - }, - options || {} - ); + { publishDelay: isLongLivedApp() ? 100 : -1, refreshPeriod: 0 }, + options || {}, + ) as ContextOptionsFull; } - private static _validateParams(params: ContextParams) { - Object.entries(params.units).forEach((entry) => { - const type = typeof entry[1]; + private static _validateParams(params: ContextParams): void { + for (const [key, value] of Object.entries(params.units)) { + const type = typeof value; if (type !== "string" && type !== "number") { throw new Error( - `Unit '${entry[0]}' UID is of unsupported type '${type}'. UID must be one of ['string', 'number']` + `Unit '${key}' UID is of unsupported type '${type}'. UID must be one of ['string', 'number']`, ); } - - if (typeof entry[1] === "string") { - if (entry[1].length === 0) { - throw new Error(`Unit '${entry[0]}' UID length must be >= 1`); - } + if (typeof value === "string" && value.length === 0) { + throw new Error(`Unit '${key}' UID length must be >= 1`); } - }); + } } } From fba6cf169116050994ffab6abe395a12326cbd8e Mon Sep 17 00:00:00 2001 From: Jonas Alves Date: Sun, 29 Mar 2026 00:25:37 +0000 Subject: [PATCH 15/22] feat: add mergeConfig utility for experiment variable injection --- src/__tests__/config.test.ts | 59 ++++++++++++++++++++++++++++++++++++ src/config.ts | 42 +++++++++++-------------- 2 files changed, 77 insertions(+), 24 deletions(-) create mode 100644 src/__tests__/config.test.ts diff --git a/src/__tests__/config.test.ts b/src/__tests__/config.test.ts new file mode 100644 index 0000000..0f9ae9b --- /dev/null +++ b/src/__tests__/config.test.ts @@ -0,0 +1,59 @@ +import { describe, expect, test, vi } from "vitest"; +import { mergeConfig } from "../config"; + +function mockContext( + variableKeys: Record, + variableValues: Record = {}, +) { + return { + variableKeys: () => variableKeys, + variableValue: (key: string, defaultValue: unknown) => + key in variableValues ? variableValues[key] : defaultValue, + }; +} + +describe("mergeConfig", () => { + test("returns new object, does not mutate original", () => { + const original = { key: "value" }; + const context = mockContext({}); + const result = mergeConfig(context as never, original); + expect(result).not.toBe(original); + expect(result).toEqual({ key: "value" }); + }); + + test("creates getter for experiment variable", () => { + const context = mockContext({ "button.color": ["exp_test"] }, { "button.color": "red" }); + const config = { button: { color: "blue" } }; + const result = mergeConfig(context as never, config); + expect(result.button).toBeDefined(); + expect((result.button as Record).color).toBe("red"); + }); + + test("falls back to default when variable not set", () => { + const context = mockContext({ "button.color": ["exp_test"] }); + const config = { button: { color: "blue" } }; + const result = mergeConfig(context as never, config); + expect((result.button as Record).color).toBe("blue"); + }); + + test("warns when overriding non-object value with object", () => { + const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); + const context = mockContext({ "button.active.color": ["exp_test"] }); + const config = { button: { active: "yes" } }; + mergeConfig(context as never, config); + expect(warnSpy).toHaveBeenCalled(); + warnSpy.mockRestore(); + }); + + test("errors when key already set by another experiment", () => { + const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + const context = mockContext({ + "button.color": ["exp_test_1"], + "button.color.shade": ["exp_test_2"], + }); + const config = { button: { color: "blue" } }; + mergeConfig(context as never, config); + expect(errorSpy).toHaveBeenCalled(); + errorSpy.mockRestore(); + }); +}); diff --git a/src/config.ts b/src/config.ts index 1be84c7..727780b 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,25 +1,27 @@ -import clone from "rfdc/default"; -import Context from "./context"; import { isObject } from "./utils"; -export function mergeConfig(context: Context, previousConfig: Record) { - const merged = clone(previousConfig); +interface ConfigContext { + variableKeys(): Record; + variableValue(key: string, defaultValue: unknown): unknown; +} + +export function mergeConfig(context: ConfigContext, previousConfig: Record): Record { + const merged = structuredClone(previousConfig); const keys = context.variableKeys(); for (const [variableKey, experimentName] of Object.entries(keys)) { - let target = merged; + let target: Record | undefined = merged; const frags = variableKey.split("."); for (let index = 0; index < frags.length; ++index) { - const frag = frags[index]; + const frag = frags[index]!; + + if (target === undefined) break; if (`_${frag}_setter` in target) { console.error( - `Config key '${frags.slice(0, index + 1).join(".")}' already set by experiment '${ - target[`_${frag}_setter`] - }'.` + `Config key '${frags.slice(0, index + 1).join(".")}' already set by experiment '${target[`_${frag}_setter`]}'.`, ); - target = undefined; break; } @@ -28,14 +30,12 @@ export function mergeConfig(context: Context, previousConfig: Record; } else { - target = target[frag]; + target = target[frag] as Record; } } } @@ -43,15 +43,9 @@ export function mergeConfig(context: Context, previousConfig: Record { - return context.variableValue(variableKey, defaultValue); - }, + get: () => context.variableValue(variableKey, defaultValue), }); } } From 9977b5a9ab5b418784629672f3c93c5d0d402c67 Mon Sep 17 00:00:00 2001 From: Jonas Alves Date: Sun, 29 Mar 2026 00:25:38 +0000 Subject: [PATCH 16/22] fix: resolve TypeScript strict mode errors in context and client --- src/client.ts | 5 ++-- src/context.ts | 66 ++++++++++++++++++++++++++++---------------------- 2 files changed, 40 insertions(+), 31 deletions(-) diff --git a/src/client.ts b/src/client.ts index 1dd54e7..d3b6072 100644 --- a/src/client.ts +++ b/src/client.ts @@ -3,6 +3,7 @@ import type { ApplicationObject, ClientOptions, ClientRequestOptions, + ContextData, ContextParams, NormalizedClientOptions, PublishParams, @@ -40,7 +41,7 @@ export class Client { this._delay = 50; } - getContext(options?: Partial): Promise { + getContext(options?: Partial): Promise { return this.getUnauthed({ ...options, path: "/context", @@ -48,7 +49,7 @@ export class Client { application: this._opts.application.name, environment: this._opts.environment, }, - }); + }) as Promise; } createContext(params: ContextParams): Promise { diff --git a/src/context.ts b/src/context.ts index ad2557b..6d043c4 100644 --- a/src/context.ts +++ b/src/context.ts @@ -29,6 +29,8 @@ interface SDKLike { getAgent(): string; getApplication(): { name: string; version: number | string }; getEnvironment(): string; + publish(request: PublishParams, options?: ClientRequestOptions): Promise; + getContext(options?: Partial): Promise; }; getContextPublisher(): ContextPublisher; getContextDataProvider(): ContextDataProvider; @@ -55,12 +57,12 @@ export class Context { private readonly _publisher: ContextPublisher; private readonly _sdk: SDKLike; private readonly _units: Units; - private _assignments: Record; + private _assignments: Record = {}; private _data!: ContextData; private _exposures: Exposure[]; private _failed: boolean; private _finalized: boolean; - private _finalizing: boolean | Promise | null; + private _finalizing: boolean | Promise | null = null; private _goals: Goal[]; private _index!: Record; private _indexVariables!: Record; @@ -372,7 +374,7 @@ export class Context { return attrs; } - private _assign(experimentName: string) { + private _assign(experimentName: string): Assignment { const experimentMatches = (experiment: ExperimentData, assignment: Assignment) => { return ( experiment.id === assignment.id && @@ -401,19 +403,19 @@ export class Context { const hasCustom = experimentName in this._cassignments; const hasOverride = experimentName in this._overrides; - const experiment = experimentName in this._index ? this._index[experimentName] : null; + const experiment = experimentName in this._index ? this._index[experimentName]! : null; if (experimentName in this._assignments) { - const assignment = this._assignments[experimentName]; + const assignment = this._assignments[experimentName]!; if (hasOverride) { - if (assignment.overridden && assignment.variant === this._overrides[experimentName]) { + if (assignment.overridden && assignment.variant === this._overrides[experimentName]!) { return assignment; } } else if (experiment == null) { if (!assignment.assigned) { return assignment; } - } else if (!hasCustom || this._cassignments[experimentName] === assignment.variant) { + } else if (!hasCustom || this._cassignments[experimentName]! === assignment.variant) { if (experimentMatches(experiment.data, assignment) && audienceMatches(experiment.data, assignment)) { return assignment; } @@ -444,7 +446,7 @@ export class Context { } assignment.overridden = true; - assignment.variant = this._overrides[experimentName]; + assignment.variant = this._overrides[experimentName]!; } else { if (experiment != null) { const unitType = experiment.data.unitType; @@ -466,7 +468,7 @@ export class Context { if (unit !== null) { const assigner = unitType in this._assigners - ? this._assigners[unitType] + ? this._assigners[unitType]! : (this._assigners[unitType] = new VariantAssigner(unit)); const eligible = assigner.assign( @@ -480,7 +482,7 @@ export class Context { if (eligible) { if (hasCustom) { - assignment.variant = this._cassignments[experimentName]; + assignment.variant = this._cassignments[experimentName]!; assignment.custom = true; } else { assignment.variant = assigner.assign( @@ -620,17 +622,20 @@ export class Context { } private _variableValue(key: string, defaultValue: string): string { - for (const i in this._indexVariables[key]) { - const experimentName = this._indexVariables[key][i].data.name; - const assignment = this._assign(experimentName); - if (assignment.variables !== undefined) { - if (!assignment.exposed) { - assignment.exposed = true; - this._queueExposure(experimentName, assignment); - } + const experiments = this._indexVariables[key]; + if (experiments) { + for (const i in experiments) { + const experimentName = experiments[i]!.data.name; + const assignment = this._assign(experimentName); + if (assignment.variables !== undefined) { + if (!assignment.exposed) { + assignment.exposed = true; + this._queueExposure(experimentName, assignment); + } - if (key in assignment.variables && (assignment.assigned || assignment.overridden)) { - return assignment.variables[key] as string; + if (key in assignment.variables && (assignment.assigned || assignment.overridden)) { + return assignment.variables[key] as string; + } } } } @@ -639,12 +644,15 @@ export class Context { } private _peekVariable(key: string, defaultValue: string): string { - for (const i in this._indexVariables[key]) { - const experimentName = this._indexVariables[key][i].data.name; - const assignment = this._assign(experimentName); - if (assignment.variables !== undefined) { - if (key in assignment.variables && (assignment.assigned || assignment.overridden)) { - return assignment.variables[key] as string; + const experiments = this._indexVariables[key]; + if (experiments) { + for (const i in experiments) { + const experimentName = experiments[i]!.data.name; + const assignment = this._assign(experimentName); + if (assignment.variables !== undefined) { + if (key in assignment.variables && (assignment.assigned || assignment.overridden)) { + return assignment.variables[key] as string; + } } } } @@ -836,18 +844,18 @@ export class Context { } } - private _unitHash(unitType: string) { + private _unitHash(unitType: string): string | null { if (!this._hashes) { this._hashes = {}; } if (!(unitType in this._hashes)) { - const hash = unitType in this._units ? hashUnit(this._units[unitType]) : null; + const hash = unitType in this._units ? hashUnit(this._units[unitType]!) : null; this._hashes[unitType] = hash; return hash; } - return this._hashes[unitType]; + return this._hashes[unitType]!; } private _init(data: ContextData, assignments: Record = {}) { From a765750797e7efd3c7096829ed5dcce2fb2f9a62 Mon Sep 17 00:00:00 2001 From: Jonas Alves Date: Sun, 29 Mar 2026 00:25:38 +0000 Subject: [PATCH 17/22] feat: add public API exports --- src/__tests__/placeholder.test.ts | 5 ----- src/index.ts | 30 +++++++++++++++++++++++++++++- 2 files changed, 29 insertions(+), 6 deletions(-) delete mode 100644 src/__tests__/placeholder.test.ts diff --git a/src/__tests__/placeholder.test.ts b/src/__tests__/placeholder.test.ts deleted file mode 100644 index ec4f8cf..0000000 --- a/src/__tests__/placeholder.test.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { expect, test } from "vitest"; - -test("placeholder", () => { - expect(true).toBe(true); -}); diff --git a/src/index.ts b/src/index.ts index 1bda49b..97a226e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1 +1,29 @@ -export const placeholder = true; +export { SDK } from "./sdk"; +export { Context } from "./context"; +export { ContextDataProvider } from "./provider"; +export { ContextPublisher } from "./publisher"; +export { mergeConfig } from "./config"; + +export type { + ApplicationObject, + Attribute, + Assignment, + ClientOptions, + ClientRequestOptions, + ContextData, + ContextParams, + CustomFieldValue, + CustomFieldValueType, + EventLogger, + EventLoggerData, + EventName, + Experiment, + ExperimentData, + Exposure, + Goal, + JSONValue, + NormalizedClientOptions, + PublishParams, + Unit, + Units, +} from "./types"; From d6f1bd5c9d9aa08feab6d9f8c08fd3ecd695792a Mon Sep 17 00:00:00 2001 From: Jonas Alves Date: Sun, 29 Mar 2026 00:41:35 +0000 Subject: [PATCH 18/22] chore: remove old source files and add implementation plan --- ...on_a9973aa3-2e87-420a-9c57-c48fd5f3a2d4.md | 45 + .../plans/2026-03-28-typescript-rewrite.md | 3763 +++++++++++++++ scripts/generate-version.js | 7 - src/__tests__/abort-controller-shim.test.js | 91 - src/__tests__/algorithm.test.js | 43 - src/__tests__/assigner.test.js | 66 - src/__tests__/client.test.js | 1071 ----- src/__tests__/config.test.js | 236 - src/__tests__/context.test.js | 4178 ----------------- src/__tests__/fetch-shim.test.js | 135 - src/__tests__/jsonexpr/evaluator.test.js | 470 -- src/__tests__/jsonexpr/jsonexpr.test.js | 69 - src/__tests__/jsonexpr/operators/and.test.js | 54 - src/__tests__/jsonexpr/operators/eq.test.js | 110 - src/__tests__/jsonexpr/operators/evaluator.js | 62 - src/__tests__/jsonexpr/operators/gt.test.js | 50 - src/__tests__/jsonexpr/operators/gte.test.js | 50 - src/__tests__/jsonexpr/operators/in.test.js | 178 - src/__tests__/jsonexpr/operators/lt.test.js | 50 - src/__tests__/jsonexpr/operators/lte.test.js | 50 - .../jsonexpr/operators/match.test.js | 35 - src/__tests__/jsonexpr/operators/not.test.js | 37 - src/__tests__/jsonexpr/operators/null.test.js | 53 - src/__tests__/jsonexpr/operators/or.test.js | 52 - .../jsonexpr/operators/semver_eq.test.js | 38 - .../jsonexpr/operators/semver_gt.test.js | 42 - .../jsonexpr/operators/semver_gte.test.js | 42 - .../jsonexpr/operators/semver_lt.test.js | 42 - .../jsonexpr/operators/semver_lte.test.js | 42 - .../jsonexpr/operators/value.test.js | 23 - src/__tests__/jsonexpr/operators/var.test.js | 18 - src/__tests__/matcher.test.js | 49 - src/__tests__/md5.test.js | 34 - src/__tests__/murmur3_32.test.js | 52 - src/__tests__/provider.test.js | 51 - src/__tests__/publisher.test.js | 56 - src/__tests__/sdk.test.js | 479 -- src/__tests__/utils.test.js | 343 -- src/abort-controller-shim.ts | 90 - src/abort.ts | 12 - src/browser.ts | 9 - src/fetch-shim.ts | 81 - src/fetch.ts | 26 - src/jsonexpr/operators/and.ts | 15 - src/jsonexpr/operators/binary.ts | 18 - src/jsonexpr/operators/eq.ts | 9 - src/jsonexpr/operators/gt.ts | 9 - src/jsonexpr/operators/gte.ts | 9 - src/jsonexpr/operators/in.ts | 23 - src/jsonexpr/operators/lt.ts | 9 - src/jsonexpr/operators/lte.ts | 9 - src/jsonexpr/operators/match.ts | 20 - src/jsonexpr/operators/not.ts | 8 - src/jsonexpr/operators/null.ts | 8 - src/jsonexpr/operators/or.ts | 15 - src/jsonexpr/operators/semver_eq.ts | 9 - src/jsonexpr/operators/semver_gt.ts | 9 - src/jsonexpr/operators/semver_gte.ts | 9 - src/jsonexpr/operators/semver_lt.ts | 9 - src/jsonexpr/operators/semver_lte.ts | 9 - src/jsonexpr/operators/unary.ts | 9 - src/jsonexpr/operators/value.ts | 7 - src/jsonexpr/operators/var.ts | 12 - src/murmur3_32.ts | 57 - src/rfdc.d.ts | 1 - 65 files changed, 3808 insertions(+), 8959 deletions(-) create mode 100644 .claude/tasks/context_session_a9973aa3-2e87-420a-9c57-c48fd5f3a2d4.md create mode 100644 docs/superpowers/plans/2026-03-28-typescript-rewrite.md delete mode 100644 scripts/generate-version.js delete mode 100644 src/__tests__/abort-controller-shim.test.js delete mode 100644 src/__tests__/algorithm.test.js delete mode 100644 src/__tests__/assigner.test.js delete mode 100644 src/__tests__/client.test.js delete mode 100644 src/__tests__/config.test.js delete mode 100644 src/__tests__/context.test.js delete mode 100644 src/__tests__/fetch-shim.test.js delete mode 100644 src/__tests__/jsonexpr/evaluator.test.js delete mode 100644 src/__tests__/jsonexpr/jsonexpr.test.js delete mode 100644 src/__tests__/jsonexpr/operators/and.test.js delete mode 100644 src/__tests__/jsonexpr/operators/eq.test.js delete mode 100644 src/__tests__/jsonexpr/operators/evaluator.js delete mode 100644 src/__tests__/jsonexpr/operators/gt.test.js delete mode 100644 src/__tests__/jsonexpr/operators/gte.test.js delete mode 100644 src/__tests__/jsonexpr/operators/in.test.js delete mode 100644 src/__tests__/jsonexpr/operators/lt.test.js delete mode 100644 src/__tests__/jsonexpr/operators/lte.test.js delete mode 100644 src/__tests__/jsonexpr/operators/match.test.js delete mode 100644 src/__tests__/jsonexpr/operators/not.test.js delete mode 100644 src/__tests__/jsonexpr/operators/null.test.js delete mode 100644 src/__tests__/jsonexpr/operators/or.test.js delete mode 100644 src/__tests__/jsonexpr/operators/semver_eq.test.js delete mode 100644 src/__tests__/jsonexpr/operators/semver_gt.test.js delete mode 100644 src/__tests__/jsonexpr/operators/semver_gte.test.js delete mode 100644 src/__tests__/jsonexpr/operators/semver_lt.test.js delete mode 100644 src/__tests__/jsonexpr/operators/semver_lte.test.js delete mode 100644 src/__tests__/jsonexpr/operators/value.test.js delete mode 100644 src/__tests__/jsonexpr/operators/var.test.js delete mode 100644 src/__tests__/matcher.test.js delete mode 100644 src/__tests__/md5.test.js delete mode 100644 src/__tests__/murmur3_32.test.js delete mode 100644 src/__tests__/provider.test.js delete mode 100644 src/__tests__/publisher.test.js delete mode 100644 src/__tests__/sdk.test.js delete mode 100644 src/__tests__/utils.test.js delete mode 100644 src/abort-controller-shim.ts delete mode 100644 src/abort.ts delete mode 100644 src/browser.ts delete mode 100644 src/fetch-shim.ts delete mode 100644 src/fetch.ts delete mode 100644 src/jsonexpr/operators/and.ts delete mode 100644 src/jsonexpr/operators/binary.ts delete mode 100644 src/jsonexpr/operators/eq.ts delete mode 100644 src/jsonexpr/operators/gt.ts delete mode 100644 src/jsonexpr/operators/gte.ts delete mode 100644 src/jsonexpr/operators/in.ts delete mode 100644 src/jsonexpr/operators/lt.ts delete mode 100644 src/jsonexpr/operators/lte.ts delete mode 100644 src/jsonexpr/operators/match.ts delete mode 100644 src/jsonexpr/operators/not.ts delete mode 100644 src/jsonexpr/operators/null.ts delete mode 100644 src/jsonexpr/operators/or.ts delete mode 100644 src/jsonexpr/operators/semver_eq.ts delete mode 100644 src/jsonexpr/operators/semver_gt.ts delete mode 100644 src/jsonexpr/operators/semver_gte.ts delete mode 100644 src/jsonexpr/operators/semver_lt.ts delete mode 100644 src/jsonexpr/operators/semver_lte.ts delete mode 100644 src/jsonexpr/operators/unary.ts delete mode 100644 src/jsonexpr/operators/value.ts delete mode 100644 src/jsonexpr/operators/var.ts delete mode 100644 src/murmur3_32.ts delete mode 100644 src/rfdc.d.ts diff --git a/.claude/tasks/context_session_a9973aa3-2e87-420a-9c57-c48fd5f3a2d4.md b/.claude/tasks/context_session_a9973aa3-2e87-420a-9c57-c48fd5f3a2d4.md new file mode 100644 index 0000000..f7bad6a --- /dev/null +++ b/.claude/tasks/context_session_a9973aa3-2e87-420a-9c57-c48fd5f3a2d4.md @@ -0,0 +1,45 @@ +# Session Context: TypeScript Rewrite Plan + +## Session ID +a9973aa3-2e87-420a-9c57-c48fd5f3a2d4 + +## Goal +Create a comprehensive implementation plan to rewrite the ABSmartly JavaScript SDK in TypeScript from scratch. + +## What Was Done +1. Created git worktree `worktree-typescript-rewrite` based on `main` +2. Thoroughly explored the entire codebase (43 source files, 34 test files, all config) +3. Read every source file, understanding all types, APIs, and algorithms +4. Read all 20 JsonExpr operators +5. Read all 14 test files to understand test patterns and fixture data +6. Wrote comprehensive 17-task implementation plan + +## Plan Location +`docs/superpowers/plans/2026-03-28-typescript-rewrite.md` + +## Key Decisions +- **Drop legacy support**: Node 18+ / modern browsers only (was Node 6+ / IE 10+) +- **Drop dependencies**: node-fetch, rfdc, core-js (use native fetch, structuredClone) +- **Drop polyfills**: fetch-shim, abort-controller-shim, platform detection wrappers +- **Drop Babel**: Use tsup (esbuild-based) for bundling ESM + CJS + browser IIFE +- **Test framework**: Vitest instead of Jest +- **Keep identical**: Public API, hashing algorithms, variant assignment, client retry logic + +## Task Breakdown (17 tasks) +1. Project scaffolding & build system (package.json, tsconfig, tsup, vitest) +2. Types & errors +3. Murmur3 hash +4. MD5 hash +5. Hashing utilities +6. Core utilities (isObject, isEqualsDeep, etc.) +7. Variant assigner +8. JsonExpr evaluator +9. JsonExpr operators (all 20 in one file) +10. JsonExpr facade & AudienceMatcher +11. HTTP client with retry +12. Provider & publisher +13. Context class (largest module) +14. SDK class +15. Config merge utility +16. Public API & index exports +17. Full integration & cleanup diff --git a/docs/superpowers/plans/2026-03-28-typescript-rewrite.md b/docs/superpowers/plans/2026-03-28-typescript-rewrite.md new file mode 100644 index 0000000..762f759 --- /dev/null +++ b/docs/superpowers/plans/2026-03-28-typescript-rewrite.md @@ -0,0 +1,3763 @@ +# ABSmartly JavaScript SDK - TypeScript Rewrite + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Rewrite the ABSmartly JavaScript SDK from scratch in strict TypeScript, modernizing the build system and dropping legacy polyfills while maintaining identical public API and hashing behavior. + +**Architecture:** Clean TypeScript with strict types throughout. All shared types in a central `types.ts` file. Hashing algorithms (murmur3, md5) ported verbatim to ensure byte-level compatibility. JsonExpr engine uses a clean operator interface. Client uses native `fetch` with retry/backoff. Build outputs ESM, CJS, and browser UMD via tsup. + +**Tech Stack:** TypeScript 5.x, Vitest, tsup (esbuild-based bundler), native fetch/AbortController (Node 18+, modern browsers) + +--- + +## Scope & Key Decisions + +**Dropping legacy support:** +- Node.js 6+ / IE 10+ -> Node.js 18+ / modern browsers (last 2 versions) +- Remove `core-js` polyfills +- Remove `node-fetch` dependency (native fetch in Node 18+) +- Remove `fetch-shim.ts` (XMLHttpRequest-based fetch polyfill) +- Remove `abort-controller-shim.ts` (native AbortController everywhere) +- Remove `abort.ts` and `fetch.ts` platform-detection wrappers +- Remove `rfdc` dependency (use `structuredClone`) +- Remove `browser.ts` (UMD entry point handled by bundler) + +**Dropping Babel entirely:** +- No more babel.config.js, no @babel/* devDependencies +- TypeScript compiler for type checking, tsup for bundling (ESM + CJS + browser) + +**Keeping identical:** +- Public API surface: `SDK`, `Context`, `ContextDataProvider`, `ContextPublisher`, `mergeConfig` +- Hashing algorithms: murmur3_32, md5 (byte-level identical output) +- JsonExpr evaluation engine and all 20 operators +- Variant assignment algorithm +- Client retry/backoff logic +- Context lifecycle (ready, publish, refresh, finalize) + +**Test framework:** +- Vitest instead of Jest (faster, native TypeScript, ESM-first) +- Same test coverage, modernized with async/await + +--- + +## File Structure + +``` +src/ + types.ts # All shared type definitions + errors.ts # Custom error classes (TimeoutError, RetryError, AbortError) + algorithm.ts # insertUniqueSorted utility + murmur3.ts # Murmur3 32-bit hash + md5.ts # MD5 hash + hashing.ts # hashUnit, base64UrlNoPadding, stringToUint8Array + utils.ts # isObject, isPromise, isEqualsDeep, arrayEqualsShallow, chooseVariant + assigner.ts # VariantAssigner + jsonexpr/ + evaluator.ts # Expression evaluation engine with type conversion + operators.ts # All 20 operators in one file (they are small) + jsonexpr.ts # JsonExpr class wiring operators to evaluator + matcher.ts # AudienceMatcher + client.ts # HTTP client with retry logic + provider.ts # ContextDataProvider + publisher.ts # ContextPublisher + context.ts # Context class + sdk.ts # SDK class + config.ts # mergeConfig utility + index.ts # Public API exports + version.ts # Generated SDK version + +src/__tests__/ + errors.test.ts + algorithm.test.ts + murmur3.test.ts + md5.test.ts + hashing.test.ts + utils.test.ts + assigner.test.ts + jsonexpr/ + evaluator.test.ts + operators.test.ts + jsonexpr.test.ts + matcher.test.ts + client.test.ts + provider.test.ts + publisher.test.ts + context.test.ts + sdk.test.ts + config.test.ts + +tsconfig.json +tsup.config.ts +vitest.config.ts +package.json +scripts/ + generate-version.ts +``` + +**Key structural changes from original:** +- `types.ts`: Centralized types instead of scattered across files +- `hashing.ts`: Extracted from `utils.ts` — hashing/encoding utilities are a distinct concern +- `jsonexpr/operators.ts`: All 20 operators consolidated into one file (each is 5-15 lines) +- Removed: `fetch.ts`, `fetch-shim.ts`, `abort.ts`, `abort-controller-shim.ts`, `browser.ts` + +--- + +## Task 1: Project Scaffolding & Build System + +**Files:** +- Create: `package.json` +- Create: `tsconfig.json` +- Create: `tsup.config.ts` +- Create: `vitest.config.ts` +- Create: `scripts/generate-version.ts` +- Create: `src/version.ts` +- Create: `.gitignore` + +- [ ] **Step 1: Delete all existing source files** + +Remove the old codebase to start fresh: + +```bash +rm -rf src/ js/ lib/ es/ dist/ types/ scripts/ +rm -f babel.config.js webpack.config.js jest.config.js .eslintrc.js .prettierrc.json .browserslistrc .editorconfig +``` + +- [ ] **Step 2: Write package.json** + +```json +{ + "name": "@absmartly/javascript-sdk", + "version": "2.0.0", + "description": "A/B Smartly Javascript SDK", + "homepage": "https://github.com/absmartly/javascript-sdk#README.md", + "bugs": "https://github.com/absmartly/javascript-sdk/issues", + "keywords": [ + "absmartly", + "ab-smartly", + "a/b-smartly", + "ab-testing", + "a/b-testing", + "split-testing", + "ab", + "a/b", + "cro" + ], + "license": "Apache-2.0", + "type": "module", + "main": "dist/index.cjs", + "module": "dist/index.js", + "browser": "dist/index.global.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" + } + } + }, + "engines": { + "node": ">=18" + }, + "scripts": { + "build": "npm run generate-version && tsup", + "test": "vitest run", + "test:watch": "vitest", + "test:coverage": "vitest run --coverage", + "typecheck": "tsc --noEmit", + "lint": "tsc --noEmit", + "generate-version": "tsx scripts/generate-version.ts", + "prepack": "npm run build" + }, + "devDependencies": { + "tsup": "^8.0.0", + "typescript": "^5.4.0", + "vitest": "^3.0.0", + "tsx": "^4.0.0", + "@vitest/coverage-v8": "^3.0.0" + }, + "publishConfig": { + "access": "public" + }, + "files": [ + "README.md", + "LICENSE", + "package.json", + "dist/" + ] +} +``` + +- [ ] **Step 3: Write tsconfig.json** + +```json +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "lib": ["ES2022", "DOM"], + "strict": true, + "skipLibCheck": true, + "sourceMap": true, + "declaration": true, + "declarationMap": true, + "outDir": "./dist", + "rootDir": "./src", + "isolatedModules": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "noUncheckedIndexedAccess": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.test.ts", "node_modules"] +} +``` + +- [ ] **Step 4: Write tsup.config.ts** + +```typescript +import { defineConfig } from "tsup"; + +export default defineConfig({ + entry: ["src/index.ts"], + format: ["esm", "cjs", "iife"], + globalName: "absmartly", + dts: true, + sourcemap: true, + clean: true, + minify: true, + target: "es2022", + outExtension({ format }) { + if (format === "iife") return { js: ".global.js" }; + return {}; + }, +}); +``` + +- [ ] **Step 5: Write vitest.config.ts** + +```typescript +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + globals: true, + environment: "node", + coverage: { + provider: "v8", + include: ["src/**/*.ts"], + exclude: ["src/**/*.test.ts", "src/version.ts"], + }, + }, +}); +``` + +- [ ] **Step 6: Write scripts/generate-version.ts** + +```typescript +import { readFileSync, writeFileSync } from "node:fs"; + +const pkg = JSON.parse(readFileSync("package.json", "utf-8")); +writeFileSync("src/version.ts", `export const SDK_VERSION = "${pkg.version}";\n`); +``` + +- [ ] **Step 7: Write src/version.ts** + +```typescript +export const SDK_VERSION = "2.0.0"; +``` + +- [ ] **Step 8: Write .gitignore** + +``` +node_modules/ +dist/ +coverage/ +*.tsbuildinfo +``` + +- [ ] **Step 9: Install dependencies** + +```bash +npm ci +``` + +- [ ] **Step 10: Verify build system works** + +Create a minimal `src/index.ts`: + +```typescript +export const placeholder = true; +``` + +Run: + +```bash +npx tsc --noEmit && npx tsup +``` + +Expected: Build succeeds, `dist/` contains `index.js`, `index.cjs`, `index.global.js`, `index.d.ts` + +- [ ] **Step 11: Verify test system works** + +Create `src/__tests__/placeholder.test.ts`: + +```typescript +import { expect, test } from "vitest"; + +test("placeholder", () => { + expect(true).toBe(true); +}); +``` + +Run: `npx vitest run` + +Expected: 1 test passes + +- [ ] **Step 12: Commit** + +```bash +git add -A +git commit -m "feat: scaffold TypeScript project with tsup, vitest, and modern config" +``` + +--- + +## Task 2: Types & Errors + +**Files:** +- Create: `src/types.ts` +- Create: `src/errors.ts` +- Create: `src/__tests__/errors.test.ts` + +- [ ] **Step 1: Write the failing tests for errors** + +```typescript +import { describe, expect, test } from "vitest"; +import { AbortError, RetryError, TimeoutError } from "../errors"; + +describe("TimeoutError", () => { + test("has correct name, message, and timeout", () => { + const error = new TimeoutError(3000); + expect(error).toBeInstanceOf(Error); + expect(error).toBeInstanceOf(TimeoutError); + expect(error.name).toBe("TimeoutError"); + expect(error.message).toBe("Timeout exceeded."); + expect(error.timeout).toBe(3000); + }); +}); + +describe("RetryError", () => { + test("has correct name, message, retries, and exception", () => { + const cause = new Error("connection refused"); + const error = new RetryError(5, cause, "https://example.com/api"); + expect(error).toBeInstanceOf(Error); + expect(error).toBeInstanceOf(RetryError); + expect(error.name).toBe("RetryError"); + expect(error.message).toBe("Retries exhausted. URL: https://example.com/api - Last Error: connection refused"); + expect(error.retries).toBe(5); + expect(error.exception).toBe(cause); + }); +}); + +describe("AbortError", () => { + test("has correct name and default message", () => { + const error = new AbortError(); + expect(error).toBeInstanceOf(Error); + expect(error).toBeInstanceOf(AbortError); + expect(error.name).toBe("AbortError"); + }); + + test("accepts custom message", () => { + const error = new AbortError("user cancelled"); + expect(error.message).toBe("user cancelled"); + }); +}); +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `npx vitest run src/__tests__/errors.test.ts` + +Expected: FAIL - modules not found + +- [ ] **Step 3: Write src/types.ts** + +```typescript +export type JSONPrimitive = string | number | boolean | null; +export type JSONObject = { [key: string]: JSONValue }; +export type JSONArray = JSONValue[]; +export type JSONValue = JSONPrimitive | JSONObject | JSONArray; + +export type CustomFieldValueType = "text" | "string" | "number" | "json" | "boolean"; + +export type CustomFieldValue = { + name: string; + value: string; + type: CustomFieldValueType; +}; + +export type ExperimentData = { + id: number; + name: string; + unitType: string | null; + iteration: number; + fullOnVariant: number; + trafficSplit: number[]; + trafficSeedHi: number; + trafficSeedLo: number; + audience: string; + audienceStrict: boolean; + split: number[]; + seedHi: number; + seedLo: number; + variants: { config: null | string }[]; + variables: Record; + variant: number; + overridden: boolean; + assigned: boolean; + exposed: boolean; + eligible: boolean; + fullOn: boolean; + custom: boolean; + audienceMismatch: boolean; + customFieldValues: CustomFieldValue[] | null; +}; + +export type Assignment = { + id: number; + iteration: number; + fullOnVariant: number; + unitType: string | null; + variant: number; + overridden: boolean; + assigned: boolean; + exposed: boolean; + eligible: boolean; + fullOn: boolean; + custom: boolean; + audienceMismatch: boolean; + trafficSplit?: number[]; + variables?: Record; + attrsSeq?: number; +}; + +export type Experiment = { + data: ExperimentData; + variables: Record[]; +}; + +export type Unit = { + type: string; + uid: string | null; +}; + +export type Exposure = { + id: number; + name: string; + exposedAt: number; + unit: string | null; + variant: number; + assigned: boolean; + eligible: boolean; + overridden: boolean; + fullOn: boolean; + custom: boolean; + audienceMismatch: boolean; +}; + +export type Attribute = { + name: string; + value: unknown; + setAt: number; +}; + +export type Units = Record; + +export type Goal = { + name: string; + properties: Record | null; + achievedAt: number; +}; + +export type ContextParams = { + units: Record; +}; + +export type ContextData = { + experiments?: ExperimentData[]; +}; + +export type FetchResponse = { + status: number; + ok: boolean; + text: () => Promise; + statusText: string; + json: () => Promise; +}; + +export type ApplicationObject = { name: string; version: number | string }; + +export type ClientRequestOptions = { + query?: Record; + path: string; + method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS"; + body?: Record; + auth?: boolean; + signal?: AbortSignal; + timeout?: number; +}; + +export type ClientOptions = { + agent?: string; + apiKey: string; + application: string | ApplicationObject; + endpoint: string; + environment: string; + retries?: number; + timeout?: number; + keepalive?: boolean; +}; + +export type NormalizedClientOptions = Omit, "application"> & { + application: ApplicationObject; +}; + +export type PublishParams = { + units: Unit[]; + publishedAt: number; + hashed: boolean; + sdkVersion: string; + attributes?: Attribute[]; + goals?: Goal[]; + exposures?: Exposure[]; +}; + +export type EventName = "error" | "ready" | "refresh" | "publish" | "exposure" | "goal" | "finalize"; + +export type EventLoggerData = Error | Exposure | Goal | ContextData | PublishParams; + +export type EventLogger = (context: unknown, eventName: EventName, data?: EventLoggerData) => void; + +export type ContextOptions = { + publisher?: { publish: (request: PublishParams, sdk: unknown, context: unknown, requestOptions?: ClientRequestOptions) => Promise }; + dataProvider?: { getContextData: (sdk: unknown, requestOptions?: Partial) => Promise }; + eventLogger?: EventLogger; + refreshPeriod: number; + publishDelay: number; + includeSystemAttributes?: boolean; +}; + +export type SDKOptions = { + client?: unknown; + eventLogger?: EventLogger; + publisher?: unknown; + provider?: unknown; +}; +``` + +- [ ] **Step 4: Write src/errors.ts** + +```typescript +export class TimeoutError extends Error { + readonly timeout: number; + constructor(timeout: number) { + super("Timeout exceeded."); + this.name = "TimeoutError"; + this.timeout = timeout; + } +} + +export class RetryError extends Error { + readonly retries: number; + readonly exception: Error; + constructor(retries: number, reason: Error, url: string) { + super(`Retries exhausted. URL: ${url} - Last Error: ${reason.message}`); + this.name = "RetryError"; + this.retries = retries; + this.exception = reason; + } +} + +export class AbortError extends Error { + constructor(message?: string) { + super(message); + this.name = "AbortError"; + } +} +``` + +- [ ] **Step 5: Run tests to verify they pass** + +Run: `npx vitest run src/__tests__/errors.test.ts` + +Expected: 3 tests pass + +- [ ] **Step 6: Verify types compile** + +Run: `npx tsc --noEmit` + +Expected: No errors + +- [ ] **Step 7: Commit** + +```bash +git add src/types.ts src/errors.ts src/__tests__/errors.test.ts +git commit -m "feat: add shared type definitions and custom error classes" +``` + +--- + +## Task 3: Murmur3 Hash + +**Files:** +- Create: `src/murmur3.ts` +- Create: `src/__tests__/murmur3.test.ts` + +- [ ] **Step 1: Write the failing tests** + +```typescript +import { describe, expect, test } from "vitest"; +import { murmur3_32 } from "../murmur3"; + +function stringToBuffer(value: string): ArrayBuffer { + const n = value.length; + const array: number[] = []; + let k = 0; + for (let i = 0; i < n; ++i) { + const c = value.charCodeAt(i); + if (c < 0x80) { + array[k++] = c; + } else if (c < 0x800) { + array[k++] = (c >> 6) | 192; + array[k++] = (c & 63) | 128; + } else { + array[k++] = (c >> 12) | 224; + array[k++] = ((c >> 6) & 63) | 128; + array[k++] = (c & 63) | 128; + } + } + return Uint8Array.from(array).buffer; +} + +describe("murmur3_32", () => { + const testCases: [string, number, number][] = [ + ["", 0x00000000, 0x00000000], + [" ", 0x00000000, 0x7ef49b98], + ["t", 0x00000000, 0xca87df4d], + ["te", 0x00000000, 0xedb8ee1b], + ["tes", 0x00000000, 0x0bb90e5a], + ["test", 0x00000000, 0xba6bd213], + ["testy", 0x00000000, 0x44af8342], + ["testy1", 0x00000000, 0x8a1a243a], + ["testy12", 0x00000000, 0x845461b9], + ["testy123", 0x00000000, 0xee0abfbc], + ["special-characters-testing-!@#$%^&*()_+-=[]{}|;':\",./<>?", 0x00000000, 0xe1b16274], + ["", 0x00000001, 0x514e28b7], + [" ", 0x00000001, 0x4f0f7132], + ["t", 0x00000001, 0x5db1831e], + ["te", 0x00000001, 0xd248bb2e], + ["tes", 0x00000001, 0xd432eb74], + ["test", 0x00000001, 0x99c02ae2], + ["testy", 0x00000001, 0xc5b2dc1e], + ["testy1", 0x00000001, 0x33925ceb], + ["testy12", 0x00000001, 0xd92c9f23], + ["testy123", 0x00000001, 0x3bc1712d], + ["special-characters-testing-!@#$%^&*()_+-=[]{}|;':\",./<>?", 0x00000001, 0x6d1d2105], + ]; + + for (const [input, seed, expected] of testCases) { + test(`murmur3_32("${input}", ${seed}) == 0x${expected.toString(16).padStart(8, "0")}`, () => { + expect(murmur3_32(stringToBuffer(input), seed)).toBe(expected); + }); + } +}); +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `npx vitest run src/__tests__/murmur3.test.ts` + +Expected: FAIL - module not found + +- [ ] **Step 3: Write src/murmur3.ts** + +```typescript +const C1 = 0xcc9e2d51; +const C2 = 0x1b873593; +const C3 = 0xe6546b64; + +const imul32 = Math.imul; + +function fmix32(h: number): number { + h ^= h >>> 16; + h = imul32(h, 0x85ebca6b); + h ^= h >>> 13; + h = imul32(h, 0xc2b2ae35); + h ^= h >>> 16; + return h >>> 0; +} + +function rotl32(a: number, b: number): number { + return (a << b) | (a >>> (32 - b)); +} + +function scramble32(block: number): number { + return imul32(rotl32(imul32(block, C1), 15), C2); +} + +export function murmur3_32(key: ArrayBufferLike, hash?: number): number { + hash = (hash || 0) >>> 0; + const dataView = new DataView(key); + + let i: number; + const n = dataView.byteLength & ~3; + for (i = 0; i < n; i += 4) { + const chunk = dataView.getUint32(i, true); + hash ^= scramble32(chunk); + hash = rotl32(hash, 13); + hash = imul32(hash, 5) + C3; + } + + let remaining = 0; + switch (dataView.byteLength & 3) { + case 3: + remaining ^= dataView.getUint8(i + 2) << 16; + // fallthrough + case 2: + remaining ^= dataView.getUint8(i + 1) << 8; + // fallthrough + case 1: + remaining ^= dataView.getUint8(i); + hash ^= scramble32(remaining); + // fallthrough + default: + break; + } + + hash ^= dataView.byteLength; + hash = fmix32(hash); + return hash >>> 0; +} +``` + +- [ ] **Step 4: Run tests to verify they pass** + +Run: `npx vitest run src/__tests__/murmur3.test.ts` + +Expected: All 22 tests pass + +- [ ] **Step 5: Commit** + +```bash +git add src/murmur3.ts src/__tests__/murmur3.test.ts +git commit -m "feat: add murmur3_32 hash implementation" +``` + +--- + +## Task 4: MD5 Hash + +**Files:** +- Create: `src/md5.ts` +- Create: `src/__tests__/md5.test.ts` + +- [ ] **Step 1: Write the failing tests** + +```typescript +import { describe, expect, test } from "vitest"; +import { md5 } from "../md5"; + +function stringToBuffer(value: string): ArrayBuffer { + const n = value.length; + const array: number[] = []; + let k = 0; + for (let i = 0; i < n; ++i) { + const c = value.charCodeAt(i); + if (c < 0x80) { + array[k++] = c; + } else if (c < 0x800) { + array[k++] = (c >> 6) | 192; + array[k++] = (c & 63) | 128; + } else { + array[k++] = (c >> 12) | 224; + array[k++] = ((c >> 6) & 63) | 128; + array[k++] = (c & 63) | 128; + } + } + return Uint8Array.from(array).buffer; +} + +function toHex(bytes: Uint8Array): string { + return Array.from(bytes) + .map((b) => b.toString(16).padStart(2, "0")) + .join(""); +} + +describe("md5", () => { + const testCases: [string, string][] = [ + ["", "d41d8cd98f00b204e9800998ecf8427e"], + ["a", "0cc175b9c0f1b6a831c399e269772661"], + ["abc", "900150983cd24fb0d6963f7d28e17f72"], + ["message digest", "f96b697d7cb7938d525a2f31aaf161d0"], + ["abcdefghijklmnopqrstuvwxyz", "c3fcd3d76192e4007dfb496cca67e13b"], + ]; + + for (const [input, expectedHex] of testCases) { + test(`md5("${input}") == ${expectedHex}`, () => { + const result = md5(stringToBuffer(input)); + expect(toHex(result)).toBe(expectedHex); + }); + } +}); +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `npx vitest run src/__tests__/md5.test.ts` + +Expected: FAIL - module not found + +- [ ] **Step 3: Write src/md5.ts** + +```typescript +function cmn(q: number, a: number, b: number, x: number, s: number, t: number): number { + a = a + q + (x >>> 0) + t; + return ((a << s) | (a >>> (32 - s))) + b; +} + +function ff(a: number, b: number, c: number, d: number, x: number, s: number, t: number): number { + return cmn((b & c) | (~b & d), a, b, x, s, t); +} + +function gg(a: number, b: number, c: number, d: number, x: number, s: number, t: number): number { + return cmn((b & d) | (c & ~d), a, b, x, s, t); +} + +function hh(a: number, b: number, c: number, d: number, x: number, s: number, t: number): number { + return cmn(b ^ c ^ d, a, b, x, s, t); +} + +function ii(a: number, b: number, c: number, d: number, x: number, s: number, t: number): number { + return cmn(c ^ (b | ~d), a, b, x, s, t); +} + +function md5cycle(x: Uint32Array, k: Uint32Array): void { + let a = x[0]!; + let b = x[1]!; + let c = x[2]!; + let d = x[3]!; + + a = ff(a, b, c, d, k[0]!, 7, -680876936); + d = ff(d, a, b, c, k[1]!, 12, -389564586); + c = ff(c, d, a, b, k[2]!, 17, 606105819); + b = ff(b, c, d, a, k[3]!, 22, -1044525330); + a = ff(a, b, c, d, k[4]!, 7, -176418897); + d = ff(d, a, b, c, k[5]!, 12, 1200080426); + c = ff(c, d, a, b, k[6]!, 17, -1473231341); + b = ff(b, c, d, a, k[7]!, 22, -45705983); + a = ff(a, b, c, d, k[8]!, 7, 1770035416); + d = ff(d, a, b, c, k[9]!, 12, -1958414417); + c = ff(c, d, a, b, k[10]!, 17, -42063); + b = ff(b, c, d, a, k[11]!, 22, -1990404162); + a = ff(a, b, c, d, k[12]!, 7, 1804603682); + d = ff(d, a, b, c, k[13]!, 12, -40341101); + c = ff(c, d, a, b, k[14]!, 17, -1502002290); + b = ff(b, c, d, a, k[15]!, 22, 1236535329); + + a = gg(a, b, c, d, k[1]!, 5, -165796510); + d = gg(d, a, b, c, k[6]!, 9, -1069501632); + c = gg(c, d, a, b, k[11]!, 14, 643717713); + b = gg(b, c, d, a, k[0]!, 20, -373897302); + a = gg(a, b, c, d, k[5]!, 5, -701558691); + d = gg(d, a, b, c, k[10]!, 9, 38016083); + c = gg(c, d, a, b, k[15]!, 14, -660478335); + b = gg(b, c, d, a, k[4]!, 20, -405537848); + a = gg(a, b, c, d, k[9]!, 5, 568446438); + d = gg(d, a, b, c, k[14]!, 9, -1019803690); + c = gg(c, d, a, b, k[3]!, 14, -187363961); + b = gg(b, c, d, a, k[8]!, 20, 1163531501); + a = gg(a, b, c, d, k[13]!, 5, -1444681467); + d = gg(d, a, b, c, k[2]!, 9, -51403784); + c = gg(c, d, a, b, k[7]!, 14, 1735328473); + b = gg(b, c, d, a, k[12]!, 20, -1926607734); + + a = hh(a, b, c, d, k[5]!, 4, -378558); + d = hh(d, a, b, c, k[8]!, 11, -2022574463); + c = hh(c, d, a, b, k[11]!, 16, 1839030562); + b = hh(b, c, d, a, k[14]!, 23, -35309556); + a = hh(a, b, c, d, k[1]!, 4, -1530992060); + d = hh(d, a, b, c, k[4]!, 11, 1272893353); + c = hh(c, d, a, b, k[7]!, 16, -155497632); + b = hh(b, c, d, a, k[10]!, 23, -1094730640); + a = hh(a, b, c, d, k[13]!, 4, 681279174); + d = hh(d, a, b, c, k[0]!, 11, -358537222); + c = hh(c, d, a, b, k[3]!, 16, -722521979); + b = hh(b, c, d, a, k[6]!, 23, 76029189); + a = hh(a, b, c, d, k[9]!, 4, -640364487); + d = hh(d, a, b, c, k[12]!, 11, -421815835); + c = hh(c, d, a, b, k[15]!, 16, 530742520); + b = hh(b, c, d, a, k[2]!, 23, -995338651); + + a = ii(a, b, c, d, k[0]!, 6, -198630844); + d = ii(d, a, b, c, k[7]!, 10, 1126891415); + c = ii(c, d, a, b, k[14]!, 15, -1416354905); + b = ii(b, c, d, a, k[5]!, 21, -57434055); + a = ii(a, b, c, d, k[12]!, 6, 1700485571); + d = ii(d, a, b, c, k[3]!, 10, -1894986606); + c = ii(c, d, a, b, k[10]!, 15, -1051523); + b = ii(b, c, d, a, k[1]!, 21, -2054922799); + a = ii(a, b, c, d, k[8]!, 6, 1873313359); + d = ii(d, a, b, c, k[15]!, 10, -30611744); + c = ii(c, d, a, b, k[6]!, 15, -1560198380); + b = ii(b, c, d, a, k[13]!, 21, 1309151649); + a = ii(a, b, c, d, k[4]!, 6, -145523070); + d = ii(d, a, b, c, k[11]!, 10, -1120210379); + c = ii(c, d, a, b, k[2]!, 15, 718787259); + b = ii(b, c, d, a, k[9]!, 21, -343485551); + + x[0] = (a + x[0]!) >>> 0; + x[1] = (b + x[1]!) >>> 0; + x[2] = (c + x[2]!) >>> 0; + x[3] = (d + x[3]!) >>> 0; +} + +export function md5(key: ArrayBufferLike): Uint8Array { + const dataView = new DataView(key); + + let i: number; + const l = dataView.byteLength; + const n = l & ~63; + const block = new Uint32Array(16); + const state = Uint32Array.of(1732584193, -271733879, -1732584194, 271733878); + for (i = 0; i < n; i += 64) { + for (let w = 0; w < 16; ++w) { + block[w] = dataView.getUint32(i + (w << 2), true); + } + md5cycle(state, block); + } + + let w = 0; + const m = l & ~3; + for (; i < m; i += 4) { + block[w++] = dataView.getUint32(i, true); + } + + const p = l & 3; + switch (p) { + case 3: + block[w++] = + 0x80000000 | dataView.getUint8(i) | (dataView.getUint8(i + 1) << 8) | (dataView.getUint8(i + 2) << 16); + break; + case 2: + block[w++] = 0x800000 | dataView.getUint8(i) | (dataView.getUint8(i + 1) << 8); + break; + case 1: + block[w++] = 0x8000 | dataView.getUint8(i); + break; + default: + block[w++] = 0x80; + break; + } + + if (w > 14) { + for (; w < 16; ++w) { + block[w] = 0; + } + md5cycle(state, block); + w = 0; + } + + for (; w < 16; ++w) { + block[w] = 0; + } + + block[14] = l << 3; + md5cycle(state, block); + return new Uint8Array(state.buffer); +} +``` + +- [ ] **Step 4: Run tests to verify they pass** + +Run: `npx vitest run src/__tests__/md5.test.ts` + +Expected: All 5 tests pass + +- [ ] **Step 5: Commit** + +```bash +git add src/md5.ts src/__tests__/md5.test.ts +git commit -m "feat: add MD5 hash implementation" +``` + +--- + +## Task 5: Hashing Utilities + +**Files:** +- Create: `src/hashing.ts` +- Create: `src/__tests__/hashing.test.ts` + +- [ ] **Step 1: Write the failing tests** + +```typescript +import { describe, expect, test } from "vitest"; +import { base64UrlNoPadding, hashUnit, stringToUint8Array } from "../hashing"; + +describe("stringToUint8Array", () => { + test("encodes ASCII", () => { + const result = stringToUint8Array("abc"); + expect(Array.from(result)).toEqual([97, 98, 99]); + }); + + test("encodes multi-byte characters", () => { + const result = stringToUint8Array("\u00e9"); + expect(Array.from(result)).toEqual([0xc3, 0xa9]); + }); + + test("encodes empty string", () => { + const result = stringToUint8Array(""); + expect(result.length).toBe(0); + }); +}); + +describe("base64UrlNoPadding", () => { + test("encodes empty", () => { + expect(base64UrlNoPadding(new Uint8Array([]))).toBe(""); + }); + + test("encodes 1 byte", () => { + expect(base64UrlNoPadding(new Uint8Array([0]))).toBe("AA"); + }); + + test("encodes 2 bytes", () => { + expect(base64UrlNoPadding(new Uint8Array([0, 0]))).toBe("AAA"); + }); + + test("encodes 3 bytes", () => { + expect(base64UrlNoPadding(new Uint8Array([0, 0, 0]))).toBe("AAAA"); + }); +}); + +describe("hashUnit", () => { + test("hashes string unit", () => { + const result = hashUnit("test_unit"); + expect(typeof result).toBe("string"); + expect(result.length).toBeGreaterThan(0); + }); + + test("hashes numeric unit", () => { + const result = hashUnit(12345); + expect(typeof result).toBe("string"); + expect(result.length).toBeGreaterThan(0); + }); + + test("produces consistent results", () => { + expect(hashUnit("abc")).toBe(hashUnit("abc")); + expect(hashUnit(123)).toBe(hashUnit(123)); + }); + + test("produces different results for different inputs", () => { + expect(hashUnit("abc")).not.toBe(hashUnit("def")); + }); +}); +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `npx vitest run src/__tests__/hashing.test.ts` + +Expected: FAIL - module not found + +- [ ] **Step 3: Write src/hashing.ts** + +```typescript +import { md5 } from "./md5"; + +export function stringToUint8Array(value: string): Uint8Array { + const n = value.length; + const array: number[] = []; + let k = 0; + for (let i = 0; i < n; ++i) { + const c = value.charCodeAt(i); + if (c < 0x80) { + array[k++] = c; + } else if (c < 0x800) { + array[k++] = (c >> 6) | 192; + array[k++] = (c & 63) | 128; + } else { + array[k++] = (c >> 12) | 224; + array[k++] = ((c >> 6) & 63) | 128; + array[k++] = (c & 63) | 128; + } + } + return Uint8Array.from(array); +} + +const BASE64_URL_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + +export function base64UrlNoPadding(value: Uint8Array): string { + const chars = BASE64_URL_CHARS; + const remaining = value.byteLength % 3; + const encodeLen = ((value.byteLength / 3) | 0) * 4 + (remaining === 0 ? 0 : remaining === 1 ? 2 : 3); + const result = new Array(encodeLen); + + let i: number; + let out = 0; + const len = value.byteLength - remaining; + for (i = 0; i < len; i += 3) { + const bytes = (value[i]! << 16) | (value[i + 1]! << 8) | value[i + 2]!; + result[out] = chars[(bytes >> 18) & 63]!; + result[out + 1] = chars[(bytes >> 12) & 63]!; + result[out + 2] = chars[(bytes >> 6) & 63]!; + result[out + 3] = chars[bytes & 63]!; + out += 4; + } + + switch (remaining) { + case 2: { + const bytes = (value[i]! << 16) | (value[i + 1]! << 8); + result[out] = chars[(bytes >> 18) & 63]!; + result[out + 1] = chars[(bytes >> 12) & 63]!; + result[out + 2] = chars[(bytes >> 6) & 63]!; + break; + } + case 1: { + const bytes = value[i]! << 16; + result[out] = chars[(bytes >> 18) & 63]!; + result[out + 1] = chars[(bytes >> 12) & 63]!; + break; + } + default: + break; + } + + return result.join(""); +} + +export function hashUnit(value: string | number): string { + const unit = typeof value === "string" ? value : value.toFixed(0); + return base64UrlNoPadding(md5(stringToUint8Array(unit).buffer)); +} +``` + +- [ ] **Step 4: Run tests to verify they pass** + +Run: `npx vitest run src/__tests__/hashing.test.ts` + +Expected: All tests pass + +- [ ] **Step 5: Commit** + +```bash +git add src/hashing.ts src/__tests__/hashing.test.ts +git commit -m "feat: add hashing utilities (stringToUint8Array, base64UrlNoPadding, hashUnit)" +``` + +--- + +## Task 6: Core Utilities + +**Files:** +- Create: `src/utils.ts` +- Create: `src/algorithm.ts` +- Create: `src/__tests__/utils.test.ts` +- Create: `src/__tests__/algorithm.test.ts` + +- [ ] **Step 1: Write the failing tests for utils** + +```typescript +import { describe, expect, test } from "vitest"; +import { arrayEqualsShallow, chooseVariant, isEqualsDeep, isObject, isPromise } from "../utils"; + +describe("isObject", () => { + test("returns true for plain objects", () => { + expect(isObject({})).toBe(true); + expect(isObject({ a: 1 })).toBe(true); + }); + + test("returns false for non-objects", () => { + expect(isObject(null)).toBe(false); + expect(isObject(undefined)).toBe(false); + expect(isObject(42)).toBe(false); + expect(isObject("str")).toBe(false); + expect(isObject([])).toBe(false); + expect(isObject(new Date())).toBe(false); + }); +}); + +describe("isPromise", () => { + test("returns true for promises", () => { + expect(isPromise(Promise.resolve())).toBe(true); + expect(isPromise({ then: () => {} })).toBe(true); + }); + + test("returns false for non-promises", () => { + expect(isPromise(null)).toBe(false); + expect(isPromise(undefined)).toBe(false); + expect(isPromise({})).toBe(false); + expect(isPromise(42)).toBe(false); + }); +}); + +describe("isEqualsDeep", () => { + test("primitives", () => { + expect(isEqualsDeep(1, 1)).toBe(true); + expect(isEqualsDeep(1, 2)).toBe(false); + expect(isEqualsDeep("a", "a")).toBe(true); + expect(isEqualsDeep("a", "b")).toBe(false); + expect(isEqualsDeep(true, true)).toBe(true); + expect(isEqualsDeep(true, false)).toBe(false); + }); + + test("NaN", () => { + expect(isEqualsDeep(NaN, NaN)).toBe(true); + }); + + test("arrays", () => { + expect(isEqualsDeep([1, 2, 3], [1, 2, 3])).toBe(true); + expect(isEqualsDeep([1, 2, 3], [1, 2, 4])).toBe(false); + expect(isEqualsDeep([1, 2], [1, 2, 3])).toBe(false); + }); + + test("objects", () => { + expect(isEqualsDeep({ a: 1, b: 2 }, { a: 1, b: 2 })).toBe(true); + expect(isEqualsDeep({ a: 1 }, { a: 2 })).toBe(false); + expect(isEqualsDeep({ a: 1 }, { a: 1, b: 2 })).toBe(false); + }); + + test("nested structures", () => { + expect(isEqualsDeep({ a: [1, { b: 2 }] }, { a: [1, { b: 2 }] })).toBe(true); + expect(isEqualsDeep({ a: [1, { b: 2 }] }, { a: [1, { b: 3 }] })).toBe(false); + }); + + test("different types", () => { + expect(isEqualsDeep(1, "1")).toBe(false); + expect(isEqualsDeep([], {})).toBe(false); + }); +}); + +describe("arrayEqualsShallow", () => { + test("same reference", () => { + const arr = [1, 2, 3]; + expect(arrayEqualsShallow(arr, arr)).toBe(true); + }); + + test("equal arrays", () => { + expect(arrayEqualsShallow([1, 2, 3], [1, 2, 3])).toBe(true); + }); + + test("different arrays", () => { + expect(arrayEqualsShallow([1, 2, 3], [1, 2, 4])).toBe(false); + }); + + test("different lengths", () => { + expect(arrayEqualsShallow([1, 2], [1, 2, 3])).toBe(false); + }); + + test("both undefined", () => { + expect(arrayEqualsShallow(undefined, undefined)).toBe(true); + }); +}); + +describe("chooseVariant", () => { + test("selects correct variant based on probability", () => { + expect(chooseVariant([0.5, 0.5], 0.0)).toBe(0); + expect(chooseVariant([0.5, 0.5], 0.4)).toBe(0); + expect(chooseVariant([0.5, 0.5], 0.5)).toBe(1); + expect(chooseVariant([0.5, 0.5], 0.9)).toBe(1); + }); + + test("three-way split", () => { + expect(chooseVariant([0.33, 0.33, 0.34], 0.0)).toBe(0); + expect(chooseVariant([0.33, 0.33, 0.34], 0.3)).toBe(0); + expect(chooseVariant([0.33, 0.33, 0.34], 0.33)).toBe(1); + expect(chooseVariant([0.33, 0.33, 0.34], 0.65)).toBe(1); + expect(chooseVariant([0.33, 0.33, 0.34], 0.66)).toBe(2); + }); + + test("returns last variant for probability >= 1", () => { + expect(chooseVariant([0.5, 0.5], 1.0)).toBe(1); + }); +}); +``` + +- [ ] **Step 2: Write the failing tests for algorithm** + +```typescript +import { describe, expect, test } from "vitest"; +import { insertUniqueSorted } from "../algorithm"; + +describe("insertUniqueSorted", () => { + test("inserts into empty array", () => { + const arr: number[] = []; + insertUniqueSorted(arr, 5, (a, b) => a < b); + expect(arr).toEqual([5]); + }); + + test("inserts in sorted order", () => { + const arr = [1, 3, 5]; + insertUniqueSorted(arr, 2, (a, b) => a < b); + expect(arr).toEqual([1, 2, 3, 5]); + }); + + test("inserts at beginning", () => { + const arr = [2, 3, 4]; + insertUniqueSorted(arr, 1, (a, b) => a < b); + expect(arr).toEqual([1, 2, 3, 4]); + }); + + test("inserts at end", () => { + const arr = [1, 2, 3]; + insertUniqueSorted(arr, 4, (a, b) => a < b); + expect(arr).toEqual([1, 2, 3, 4]); + }); + + test("does not insert duplicate", () => { + const arr = [1, 2, 3]; + insertUniqueSorted(arr, 2, (a, b) => a < b); + expect(arr).toEqual([1, 2, 3]); + }); +}); +``` + +- [ ] **Step 3: Run tests to verify they fail** + +Run: `npx vitest run src/__tests__/utils.test.ts src/__tests__/algorithm.test.ts` + +Expected: FAIL - modules not found + +- [ ] **Step 4: Write src/utils.ts** + +```typescript +export function isObject(value: unknown): value is Record { + if (!(value instanceof Object)) return false; + const proto = Object.getPrototypeOf(value); + return proto == null || proto === Object.prototype; +} + +export function isPromise(value: unknown): value is Promise { + return value !== null && typeof value === "object" && typeof (value as Promise).then === "function"; +} + +function arrayEqualsDeep(a: unknown[], b: unknown[], astack: unknown[] = [], bstack: unknown[] = []): boolean { + let len = astack.length; + while (len--) { + if (astack[len] === a) return bstack[len] === b; + } + + astack.push(a); + bstack.push(b); + + len = a.length; + while (len--) { + if (!isEqualsDeep(a[len], b[len], astack, bstack)) return false; + } + + bstack.pop(); + astack.pop(); + + return true; +} + +function objectEqualsDeep( + a: Record, + b: Record, + keys: string[], + astack?: unknown[], + bstack?: unknown[], +): boolean { + let len = astack?.length ?? 0; + while (len--) { + if (astack && astack[len] === a) return bstack !== undefined && bstack[len] === b; + } + + astack = astack ?? []; + bstack = bstack ?? []; + + astack.push(a); + bstack.push(b); + + len = keys.length; + while (len--) { + const key = keys[len]!; + if (!Object.prototype.hasOwnProperty.call(b, key)) return false; + if (!isEqualsDeep(a[key], b[key], astack, bstack)) return false; + } + + bstack.pop(); + astack.pop(); + + return true; +} + +export function isEqualsDeep(a: unknown, b: unknown, astack?: unknown[], bstack?: unknown[]): boolean { + if (a === b) return true; + if (typeof a !== typeof b) return false; + + switch (typeof a) { + case "boolean": + return a === b; + case "number": + if (Number.isNaN(a)) return Number.isNaN(b as number); + return a === b; + case "string": + return a === b; + case "object": { + const arrays = Array.isArray(a); + if (arrays && !Array.isArray(b)) return false; + + const objects = isObject(a); + if (objects && !isObject(b)) return false; + + if (!arrays && !objects) return false; + + if (arrays && Array.isArray(b)) { + if (a.length === b.length) { + return arrayEqualsDeep(a, b, astack, bstack); + } + } else if (a && b) { + const keys = Object.keys(a); + if (keys.length === Object.keys(b as Record).length) { + return objectEqualsDeep( + a as Record, + b as Record, + keys, + astack, + bstack, + ); + } + } + break; + } + default: + break; + } + return false; +} + +export function arrayEqualsShallow(a?: unknown[], b?: unknown[]): boolean { + return a === b || (a?.length === b?.length && !a?.some((va, vi) => b && va !== b[vi])); +} + +export function chooseVariant(split: number[], prob: number): number { + let cumSum = 0.0; + for (let i = 0; i < split.length; ++i) { + cumSum += split[i]!; + if (prob < cumSum) return i; + } + return split.length - 1; +} +``` + +- [ ] **Step 5: Write src/algorithm.ts** + +```typescript +export function insertUniqueSorted( + arr: TData[], + value: TData, + isSorted: (a: TData, b: TData) => boolean, +): void { + let left = 0; + let right = arr.length - 1; + + while (left <= right) { + const mid = Math.floor(left + (right - left) / 2); + if (isSorted(arr[mid]!, value)) { + left = mid + 1; + } else if (isSorted(value, arr[mid]!)) { + right = mid - 1; + } else { + return; + } + } + + arr.splice(left, 0, value); +} +``` + +- [ ] **Step 6: Run tests to verify they pass** + +Run: `npx vitest run src/__tests__/utils.test.ts src/__tests__/algorithm.test.ts` + +Expected: All tests pass + +- [ ] **Step 7: Commit** + +```bash +git add src/utils.ts src/algorithm.ts src/__tests__/utils.test.ts src/__tests__/algorithm.test.ts +git commit -m "feat: add core utilities (isObject, isEqualsDeep, chooseVariant, insertUniqueSorted)" +``` + +--- + +## Task 7: Variant Assigner + +**Files:** +- Create: `src/assigner.ts` +- Create: `src/__tests__/assigner.test.ts` + +- [ ] **Step 1: Write the failing tests** + +```typescript +import { describe, expect, test } from "vitest"; +import { VariantAssigner } from "../assigner"; +import { hashUnit } from "../hashing"; + +describe("VariantAssigner", () => { + const testCases: Record = { + "bleh@absmartly.com": [ + [[0.5, 0.5], 0x00000000, 0x00000000, 0], + [[0.5, 0.5], 0x00000000, 0x00000001, 1], + [[0.5, 0.5], 0x8015406f, 0x7ef49b98, 0], + [[0.5, 0.5], 0x3b2e7571, 0xca87df4d, 0], + [[0.33, 0.33, 0.34], 0x00000000, 0x00000000, 0], + [[0.33, 0.33, 0.34], 0x00000000, 0x00000001, 2], + [[0.33, 0.33, 0.34], 0x8015406f, 0x7ef49b98, 0], + [[0.33, 0.33, 0.34], 0x3b2e7571, 0xca87df4d, 0], + ], + "123456789": [ + [[0.5, 0.5], 0x00000000, 0x00000000, 1], + [[0.5, 0.5], 0x00000000, 0x00000001, 0], + [[0.5, 0.5], 0x8015406f, 0x7ef49b98, 1], + [[0.5, 0.5], 0x3b2e7571, 0xca87df4d, 1], + [[0.33, 0.33, 0.34], 0x00000000, 0x00000000, 2], + [[0.33, 0.33, 0.34], 0x00000000, 0x00000001, 0], + [[0.33, 0.33, 0.34], 0x8015406f, 0x7ef49b98, 2], + [[0.33, 0.33, 0.34], 0x3b2e7571, 0xca87df4d, 1], + ], + }; + + for (const [unit, cases] of Object.entries(testCases)) { + describe(`unit: "${unit}"`, () => { + const assigner = new VariantAssigner(hashUnit(unit)); + for (const [split, seedHi, seedLo, expected] of cases) { + test(`assign([${split}], 0x${seedHi.toString(16)}, 0x${seedLo.toString(16)}) == ${expected}`, () => { + expect(assigner.assign(split, seedHi, seedLo)).toBe(expected); + }); + } + }); + } +}); +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `npx vitest run src/__tests__/assigner.test.ts` + +Expected: FAIL - module not found + +- [ ] **Step 3: Write src/assigner.ts** + +```typescript +import { chooseVariant } from "./utils"; +import { stringToUint8Array } from "./hashing"; +import { murmur3_32 } from "./murmur3"; + +export class VariantAssigner { + private readonly _unitHash: number; + + constructor(unit: string) { + this._unitHash = murmur3_32(stringToUint8Array(unit).buffer); + } + + assign(split: number[], seedHi: number, seedLo: number): number { + const prob = this._probability(seedHi, seedLo); + return chooseVariant(split, prob); + } + + private _probability(seedHi: number, seedLo: number): number { + const key = this._unitHash; + const buffer = new ArrayBuffer(12); + const view = new DataView(buffer); + view.setUint32(0, seedLo, true); + view.setUint32(4, seedHi, true); + view.setUint32(8, key, true); + return murmur3_32(buffer) * (1.0 / 0xffffffff); + } +} +``` + +- [ ] **Step 4: Run tests to verify they pass** + +Run: `npx vitest run src/__tests__/assigner.test.ts` + +Expected: All 16 tests pass + +- [ ] **Step 5: Commit** + +```bash +git add src/assigner.ts src/__tests__/assigner.test.ts +git commit -m "feat: add VariantAssigner with murmur3-based probability" +``` + +--- + +## Task 8: JsonExpr Evaluator + +**Files:** +- Create: `src/jsonexpr/evaluator.ts` +- Create: `src/__tests__/jsonexpr/evaluator.test.ts` + +- [ ] **Step 1: Write the failing tests** + +```typescript +import { describe, expect, test } from "vitest"; +import { Evaluator } from "../../jsonexpr/evaluator"; + +function createEvaluator(vars: Record = {}) { + return new Evaluator({}, vars); +} + +describe("Evaluator", () => { + describe("booleanConvert", () => { + const evaluator = createEvaluator(); + + test("boolean values", () => { + expect(evaluator.booleanConvert(true)).toBe(true); + expect(evaluator.booleanConvert(false)).toBe(false); + }); + + test("number values", () => { + expect(evaluator.booleanConvert(1)).toBe(true); + expect(evaluator.booleanConvert(0)).toBe(false); + expect(evaluator.booleanConvert(-1)).toBe(true); + }); + + test("string values", () => { + expect(evaluator.booleanConvert("true")).toBe(true); + expect(evaluator.booleanConvert("false")).toBe(false); + expect(evaluator.booleanConvert("0")).toBe(false); + expect(evaluator.booleanConvert("")).toBe(false); + expect(evaluator.booleanConvert("abc")).toBe(true); + }); + + test("null/undefined", () => { + expect(evaluator.booleanConvert(null)).toBe(false); + expect(evaluator.booleanConvert(undefined)).toBe(false); + }); + }); + + describe("numberConvert", () => { + const evaluator = createEvaluator(); + + test("number values", () => { + expect(evaluator.numberConvert(42)).toBe(42); + expect(evaluator.numberConvert(0)).toBe(0); + expect(evaluator.numberConvert(-1.5)).toBe(-1.5); + }); + + test("boolean values", () => { + expect(evaluator.numberConvert(true)).toBe(1); + expect(evaluator.numberConvert(false)).toBe(0); + }); + + test("string values", () => { + expect(evaluator.numberConvert("42")).toBe(42); + expect(evaluator.numberConvert("3.14")).toBe(3.14); + expect(evaluator.numberConvert("abc")).toBe(null); + }); + + test("other types", () => { + expect(evaluator.numberConvert(null)).toBe(null); + expect(evaluator.numberConvert({})).toBe(null); + }); + }); + + describe("stringConvert", () => { + const evaluator = createEvaluator(); + + test("string values", () => { + expect(evaluator.stringConvert("hello")).toBe("hello"); + }); + + test("boolean values", () => { + expect(evaluator.stringConvert(true)).toBe("true"); + expect(evaluator.stringConvert(false)).toBe("false"); + }); + + test("number values", () => { + expect(evaluator.stringConvert(42)).toBe("42"); + expect(evaluator.stringConvert(0)).toBe("0"); + }); + + test("other types", () => { + expect(evaluator.stringConvert(null)).toBe(null); + expect(evaluator.stringConvert({})).toBe(null); + }); + }); + + describe("extractVar", () => { + test("extracts top-level variable", () => { + const evaluator = createEvaluator({ name: "John" }); + expect(evaluator.extractVar("name")).toBe("John"); + }); + + test("extracts nested variable", () => { + const evaluator = createEvaluator({ user: { name: "John" } }); + expect(evaluator.extractVar("user/name")).toBe("John"); + }); + + test("returns null for missing path", () => { + const evaluator = createEvaluator({ name: "John" }); + expect(evaluator.extractVar("missing")).toBe(null); + }); + }); + + describe("compare", () => { + const evaluator = createEvaluator(); + + test("numbers", () => { + expect(evaluator.compare(1, 2)).toBe(-1); + expect(evaluator.compare(2, 1)).toBe(1); + expect(evaluator.compare(1, 1)).toBe(0); + }); + + test("strings", () => { + expect(evaluator.compare("a", "b")).toBe(-1); + expect(evaluator.compare("b", "a")).toBe(1); + expect(evaluator.compare("a", "a")).toBe(0); + }); + + test("booleans", () => { + expect(evaluator.compare(true, true)).toBe(0); + expect(evaluator.compare(false, false)).toBe(0); + }); + + test("null handling", () => { + expect(evaluator.compare(null, null)).toBe(0); + expect(evaluator.compare(null, 1)).toBe(null); + expect(evaluator.compare(1, null)).toBe(null); + }); + }); + + describe("versionCompare", () => { + const evaluator = createEvaluator(); + + test("equal versions", () => { + expect(evaluator.versionCompare("1.0.0", "1.0.0")).toBe(0); + }); + + test("greater version", () => { + expect(evaluator.versionCompare("2.0.0", "1.0.0")).toBe(1); + }); + + test("lesser version", () => { + expect(evaluator.versionCompare("1.0.0", "2.0.0")).toBe(-1); + }); + + test("prerelease is less than release", () => { + expect(evaluator.versionCompare("1.0.0-alpha", "1.0.0")).toBe(-1); + }); + + test("v prefix", () => { + expect(evaluator.versionCompare("v1.0.0", "1.0.0")).toBe(0); + }); + + test("build metadata ignored", () => { + expect(evaluator.versionCompare("1.0.0+build1", "1.0.0+build2")).toBe(0); + }); + + test("null inputs", () => { + expect(evaluator.versionCompare(null, "1.0.0")).toBe(null); + expect(evaluator.versionCompare("1.0.0", null)).toBe(null); + }); + + test("empty inputs", () => { + expect(evaluator.versionCompare("", "1.0.0")).toBe(null); + expect(evaluator.versionCompare("1.0.0", "")).toBe(null); + }); + }); +}); +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `npx vitest run src/__tests__/jsonexpr/evaluator.test.ts` + +Expected: FAIL - module not found + +- [ ] **Step 3: Write src/jsonexpr/evaluator.ts** + +Port the evaluator verbatim from the original. The implementation is in the code read earlier (`src/jsonexpr/evaluator.ts` from original codebase). Copy it with proper strict TypeScript typing: + +```typescript +import { isEqualsDeep, isObject } from "../utils"; + +export interface Operator { + evaluate(evaluator: Evaluator, args: unknown): unknown; +} + +function parseSemver(version: string) { + let v = version; + if (v.startsWith("v") || v.startsWith("V")) { + v = v.substring(1); + } + + const plusIndex = v.indexOf("+"); + if (plusIndex >= 0) { + v = v.substring(0, plusIndex); + } + + if (v === "") return null; + + const [core, ...preReleaseParts] = v.split("-"); + const preRelease = preReleaseParts.join("-"); + + if (core === "") return null; + + const parts = core!.split("."); + return { parts, preRelease }; +} + +const NUMERIC_IDENTIFIER = /^\d+$/; + +function stripLeadingZeros(s: string): string { + const stripped = s.replace(/^0+/, ""); + return stripped === "" ? "0" : stripped; +} + +function compareIdentifiers(a: string, b: string): number { + const aIsNum = NUMERIC_IDENTIFIER.test(a); + const bIsNum = NUMERIC_IDENTIFIER.test(b); + + if (aIsNum && bIsNum) { + const aNorm = stripLeadingZeros(a); + const bNorm = stripLeadingZeros(b); + if (aNorm.length !== bNorm.length) return aNorm.length > bNorm.length ? 1 : -1; + return aNorm === bNorm ? 0 : aNorm > bNorm ? 1 : -1; + } + if (aIsNum) return -1; + if (bIsNum) return 1; + return a === b ? 0 : a > b ? 1 : -1; +} + +export class Evaluator { + private readonly operators: Record; + private readonly vars: Record; + + constructor(operators: Record, vars: Record) { + this.operators = operators; + this.vars = vars; + } + + evaluate(expr: unknown): unknown { + if (Array.isArray(expr)) { + return this.operators["and"]?.evaluate(this, expr) ?? null; + } else if (isObject(expr)) { + for (const [key, value] of Object.entries(expr)) { + const op = this.operators[key]; + if (op !== undefined) { + return op.evaluate(this, value); + } + break; + } + } + return null; + } + + booleanConvert(x: unknown): boolean { + const type = typeof x; + switch (type) { + case "boolean": + return x as boolean; + case "number": + return x !== 0; + case "string": + return x !== "false" && x !== "0" && x !== ""; + default: + return x !== null && x !== undefined; + } + } + + numberConvert(x: unknown): number | null { + switch (typeof x) { + case "number": + return x; + case "boolean": + return x ? 1 : 0; + case "string": { + const y = parseFloat(x); + return Number.isFinite(y) ? y : null; + } + default: + return null; + } + } + + stringConvert(x: unknown): string | null { + switch (typeof x) { + case "string": + return x; + case "boolean": + return x.toString(); + case "number": + return x.toFixed(15).replace(/\.?0{0,15}$/, ""); + default: + return null; + } + } + + extractVar(path: string): unknown { + const frags = path.split("/"); + let target: unknown = this.vars ?? {}; + for (const frag of frags) { + if (target !== null && typeof target === "object" && frag in (target as Record)) { + target = (target as Record)[frag]; + continue; + } + return null; + } + return target; + } + + versionCompare(lhs: unknown, rhs: unknown): number | null { + const lhsStr = this.stringConvert(lhs); + const rhsStr = this.stringConvert(rhs); + if (lhsStr === null || rhsStr === null || lhsStr === "" || rhsStr === "") return null; + + const l = parseSemver(lhsStr); + const r = parseSemver(rhsStr); + if (l === null || r === null) return null; + + const maxLen = Math.max(l.parts.length, r.parts.length); + for (let i = 0; i < maxLen; i++) { + const lPart = l.parts[i] ?? "0"; + const rPart = r.parts[i] ?? "0"; + const result = compareIdentifiers(lPart, rPart); + if (result !== 0) return result; + } + + if (!l.preRelease && !r.preRelease) return 0; + if (!l.preRelease) return 1; + if (!r.preRelease) return -1; + + const lPreParts = l.preRelease.split("."); + const rPreParts = r.preRelease.split("."); + const preLen = Math.max(lPreParts.length, rPreParts.length); + for (let i = 0; i < preLen; i++) { + if (i >= lPreParts.length) return -1; + if (i >= rPreParts.length) return 1; + const result = compareIdentifiers(lPreParts[i]!, rPreParts[i]!); + if (result !== 0) return result; + } + + return 0; + } + + compare(lhs: unknown, rhs: unknown): number | null { + if (lhs === null) return rhs === null ? 0 : null; + if (rhs === null) return null; + + switch (typeof lhs) { + case "number": { + const rvalue = this.numberConvert(rhs); + if (rvalue !== null) return lhs === rvalue ? 0 : lhs > rvalue ? 1 : -1; + break; + } + case "string": { + const rvalue = this.stringConvert(rhs); + if (rvalue !== null) return lhs === rvalue ? 0 : lhs > rvalue ? 1 : -1; + break; + } + case "boolean": { + const rvalue = this.booleanConvert(rhs); + if (rvalue != null) return lhs === rvalue ? 0 : lhs > rvalue ? 1 : -1; + break; + } + default: { + if (isEqualsDeep(lhs, rhs)) return 0; + break; + } + } + + return null; + } +} +``` + +- [ ] **Step 4: Run tests to verify they pass** + +Run: `npx vitest run src/__tests__/jsonexpr/evaluator.test.ts` + +Expected: All tests pass + +- [ ] **Step 5: Commit** + +```bash +git add src/jsonexpr/evaluator.ts src/__tests__/jsonexpr/evaluator.test.ts +git commit -m "feat: add JsonExpr evaluator with type conversion and comparison" +``` + +--- + +## Task 9: JsonExpr Operators + +**Files:** +- Create: `src/jsonexpr/operators.ts` +- Create: `src/__tests__/jsonexpr/operators.test.ts` + +- [ ] **Step 1: Write the failing tests** + +```typescript +import { describe, expect, test } from "vitest"; +import { Evaluator } from "../../jsonexpr/evaluator"; +import { + AndCombinator, + EqualsOperator, + GreaterThanOperator, + GreaterThanOrEqualOperator, + InOperator, + LessThanOperator, + LessThanOrEqualOperator, + MatchOperator, + NotOperator, + NullOperator, + OrCombinator, + SemverEqualsOperator, + SemverGreaterThanOperator, + SemverGreaterThanOrEqualOperator, + SemverLessThanOperator, + SemverLessThanOrEqualOperator, + ValueOperator, + VarOperator, +} from "../../jsonexpr/operators"; + +function makeEvaluator(vars: Record = {}): Evaluator { + const operators = { + and: new AndCombinator(), + or: new OrCombinator(), + value: new ValueOperator(), + var: new VarOperator(), + null: new NullOperator(), + not: new NotOperator(), + in: new InOperator(), + match: new MatchOperator(), + eq: new EqualsOperator(), + gt: new GreaterThanOperator(), + gte: new GreaterThanOrEqualOperator(), + lt: new LessThanOperator(), + lte: new LessThanOrEqualOperator(), + semver_eq: new SemverEqualsOperator(), + semver_gt: new SemverGreaterThanOperator(), + semver_gte: new SemverGreaterThanOrEqualOperator(), + semver_lt: new SemverLessThanOperator(), + semver_lte: new SemverLessThanOrEqualOperator(), + }; + return new Evaluator(operators, vars); +} + +describe("ValueOperator", () => { + test("returns the value as-is", () => { + const evaluator = makeEvaluator(); + const op = new ValueOperator(); + expect(op.evaluate(evaluator, 42)).toBe(42); + expect(op.evaluate(evaluator, "hello")).toBe("hello"); + expect(op.evaluate(evaluator, null)).toBe(null); + }); +}); + +describe("VarOperator", () => { + test("extracts variable by path string", () => { + const evaluator = makeEvaluator({ name: "John", nested: { key: "val" } }); + const op = new VarOperator(); + expect(op.evaluate(evaluator, "name")).toBe("John"); + expect(op.evaluate(evaluator, "nested/key")).toBe("val"); + }); + + test("extracts variable by path object", () => { + const evaluator = makeEvaluator({ name: "John" }); + const op = new VarOperator(); + expect(op.evaluate(evaluator, { path: "name" })).toBe("John"); + }); + + test("returns null for non-string path", () => { + const evaluator = makeEvaluator(); + const op = new VarOperator(); + expect(op.evaluate(evaluator, 42)).toBe(null); + }); +}); + +describe("AndCombinator", () => { + test("returns true when all truthy", () => { + const evaluator = makeEvaluator(); + const op = new AndCombinator(); + expect(op.evaluate(evaluator, [{ value: true }, { value: 1 }])).toBe(true); + }); + + test("returns false when any falsy", () => { + const evaluator = makeEvaluator(); + const op = new AndCombinator(); + expect(op.evaluate(evaluator, [{ value: true }, { value: false }])).toBe(false); + }); + + test("returns true for empty array", () => { + const evaluator = makeEvaluator(); + const op = new AndCombinator(); + expect(op.evaluate(evaluator, [])).toBe(true); + }); +}); + +describe("OrCombinator", () => { + test("returns true when any truthy", () => { + const evaluator = makeEvaluator(); + const op = new OrCombinator(); + expect(op.evaluate(evaluator, [{ value: false }, { value: true }])).toBe(true); + }); + + test("returns false when all falsy", () => { + const evaluator = makeEvaluator(); + const op = new OrCombinator(); + expect(op.evaluate(evaluator, [{ value: false }, { value: 0 }])).toBe(false); + }); + + test("returns true for empty array (vacuous truth)", () => { + const evaluator = makeEvaluator(); + const op = new OrCombinator(); + expect(op.evaluate(evaluator, [])).toBe(true); + }); +}); + +describe("NotOperator", () => { + test("negates boolean", () => { + const evaluator = makeEvaluator(); + const op = new NotOperator(); + expect(op.evaluate(evaluator, { value: true })).toBe(false); + expect(op.evaluate(evaluator, { value: false })).toBe(true); + }); +}); + +describe("NullOperator", () => { + test("checks if value is null", () => { + const evaluator = makeEvaluator(); + const op = new NullOperator(); + expect(op.evaluate(evaluator, { value: null })).toBe(true); + expect(op.evaluate(evaluator, { value: 0 })).toBe(false); + }); +}); + +describe("EqualsOperator", () => { + test("compares values for equality", () => { + const evaluator = makeEvaluator(); + const op = new EqualsOperator(); + expect(op.evaluate(evaluator, [{ value: 1 }, { value: 1 }])).toBe(true); + expect(op.evaluate(evaluator, [{ value: 1 }, { value: 2 }])).toBe(false); + expect(op.evaluate(evaluator, [{ value: "a" }, { value: "a" }])).toBe(true); + }); +}); + +describe("Comparison operators", () => { + const evaluator = makeEvaluator(); + + test("GreaterThan", () => { + const op = new GreaterThanOperator(); + expect(op.evaluate(evaluator, [{ value: 2 }, { value: 1 }])).toBe(true); + expect(op.evaluate(evaluator, [{ value: 1 }, { value: 2 }])).toBe(false); + expect(op.evaluate(evaluator, [{ value: 1 }, { value: 1 }])).toBe(false); + }); + + test("GreaterThanOrEqual", () => { + const op = new GreaterThanOrEqualOperator(); + expect(op.evaluate(evaluator, [{ value: 2 }, { value: 1 }])).toBe(true); + expect(op.evaluate(evaluator, [{ value: 1 }, { value: 1 }])).toBe(true); + expect(op.evaluate(evaluator, [{ value: 1 }, { value: 2 }])).toBe(false); + }); + + test("LessThan", () => { + const op = new LessThanOperator(); + expect(op.evaluate(evaluator, [{ value: 1 }, { value: 2 }])).toBe(true); + expect(op.evaluate(evaluator, [{ value: 2 }, { value: 1 }])).toBe(false); + }); + + test("LessThanOrEqual", () => { + const op = new LessThanOrEqualOperator(); + expect(op.evaluate(evaluator, [{ value: 1 }, { value: 2 }])).toBe(true); + expect(op.evaluate(evaluator, [{ value: 1 }, { value: 1 }])).toBe(true); + }); +}); + +describe("InOperator", () => { + const evaluator = makeEvaluator(); + const op = new InOperator(); + + test("array membership", () => { + expect(op.evaluate(evaluator, [{ value: [1, 2, 3] }, { value: 2 }])).toBe(true); + expect(op.evaluate(evaluator, [{ value: [1, 2, 3] }, { value: 4 }])).toBe(false); + }); + + test("string containment", () => { + expect(op.evaluate(evaluator, [{ value: "hello world" }, { value: "world" }])).toBe(true); + expect(op.evaluate(evaluator, [{ value: "hello" }, { value: "world" }])).toBe(false); + }); + + test("object key check", () => { + expect(op.evaluate(evaluator, [{ value: { a: 1 } }, { value: "a" }])).toBe(true); + expect(op.evaluate(evaluator, [{ value: { a: 1 } }, { value: "b" }])).toBe(false); + }); +}); + +describe("MatchOperator", () => { + const evaluator = makeEvaluator(); + const op = new MatchOperator(); + + test("matches regex", () => { + expect(op.evaluate(evaluator, [{ value: "hello123" }, { value: "\\d+" }])).toBe(true); + expect(op.evaluate(evaluator, [{ value: "hello" }, { value: "\\d+" }])).toBe(false); + }); +}); + +describe("Semver operators", () => { + const evaluator = makeEvaluator(); + + test("semver_eq", () => { + const op = new SemverEqualsOperator(); + expect(op.evaluate(evaluator, [{ value: "1.0.0" }, { value: "1.0.0" }])).toBe(true); + expect(op.evaluate(evaluator, [{ value: "1.0.0" }, { value: "1.0.1" }])).toBe(false); + }); + + test("semver_gt", () => { + const op = new SemverGreaterThanOperator(); + expect(op.evaluate(evaluator, [{ value: "2.0.0" }, { value: "1.0.0" }])).toBe(true); + expect(op.evaluate(evaluator, [{ value: "1.0.0" }, { value: "2.0.0" }])).toBe(false); + }); + + test("semver_gte", () => { + const op = new SemverGreaterThanOrEqualOperator(); + expect(op.evaluate(evaluator, [{ value: "1.0.0" }, { value: "1.0.0" }])).toBe(true); + }); + + test("semver_lt", () => { + const op = new SemverLessThanOperator(); + expect(op.evaluate(evaluator, [{ value: "1.0.0" }, { value: "2.0.0" }])).toBe(true); + }); + + test("semver_lte", () => { + const op = new SemverLessThanOrEqualOperator(); + expect(op.evaluate(evaluator, [{ value: "1.0.0" }, { value: "1.0.0" }])).toBe(true); + }); +}); +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `npx vitest run src/__tests__/jsonexpr/operators.test.ts` + +Expected: FAIL - module not found + +- [ ] **Step 3: Write src/jsonexpr/operators.ts** + +All 20 operators consolidated in one file: + +```typescript +import { Evaluator } from "./evaluator"; +import { isObject } from "../utils"; + +export class ValueOperator { + evaluate(_: Evaluator, value: unknown): unknown { + return value; + } +} + +export class VarOperator { + evaluate(evaluator: Evaluator, path: unknown): unknown { + if (isObject(path)) { + path = (path as { path: string }).path; + } + return typeof path === "string" ? evaluator.extractVar(path) : null; + } +} + +export class AndCombinator { + evaluate(evaluator: Evaluator, args: unknown): boolean | null { + if (Array.isArray(args)) { + for (const expr of args) { + if (!evaluator.booleanConvert(evaluator.evaluate(expr))) return false; + } + return true; + } + return null; + } +} + +export class OrCombinator { + evaluate(evaluator: Evaluator, args: unknown): boolean | null { + if (Array.isArray(args)) { + for (const expr of args) { + if (evaluator.booleanConvert(evaluator.evaluate(expr))) return true; + } + return args.length === 0; + } + return null; + } +} + +abstract class UnaryOperator { + abstract unary(evaluator: Evaluator, arg: unknown): boolean; + evaluate(evaluator: Evaluator, arg: unknown): boolean { + arg = evaluator.evaluate(arg); + return this.unary(evaluator, arg); + } +} + +export class NotOperator extends UnaryOperator { + unary(evaluator: Evaluator, arg: unknown): boolean { + return !evaluator.booleanConvert(arg); + } +} + +export class NullOperator extends UnaryOperator { + unary(_: Evaluator, value: unknown): boolean { + return value === null; + } +} + +abstract class BinaryOperator { + abstract binary(evaluator: Evaluator, lhs: unknown, rhs: unknown): boolean | null; + evaluate(evaluator: Evaluator, args: unknown): boolean | null { + if (Array.isArray(args)) { + const lhs = args.length > 0 ? evaluator.evaluate(args[0]) : null; + if (lhs !== null) { + const rhs = args.length > 1 ? evaluator.evaluate(args[1]) : null; + if (rhs !== null) { + return this.binary(evaluator, lhs, rhs); + } + } + } + return null; + } +} + +export class EqualsOperator extends BinaryOperator { + binary(evaluator: Evaluator, lhs: unknown, rhs: unknown): boolean | null { + const result = evaluator.compare(lhs, rhs); + return result !== null ? result === 0 : null; + } +} + +export class GreaterThanOperator extends BinaryOperator { + binary(evaluator: Evaluator, lhs: unknown, rhs: unknown): boolean | null { + const result = evaluator.compare(lhs, rhs); + return result !== null ? result > 0 : null; + } +} + +export class GreaterThanOrEqualOperator extends BinaryOperator { + binary(evaluator: Evaluator, lhs: unknown, rhs: unknown): boolean | null { + const result = evaluator.compare(lhs, rhs); + return result !== null ? result >= 0 : null; + } +} + +export class LessThanOperator extends BinaryOperator { + binary(evaluator: Evaluator, lhs: unknown, rhs: unknown): boolean | null { + const result = evaluator.compare(lhs, rhs); + return result !== null ? result < 0 : null; + } +} + +export class LessThanOrEqualOperator extends BinaryOperator { + binary(evaluator: Evaluator, lhs: unknown, rhs: unknown): boolean | null { + const result = evaluator.compare(lhs, rhs); + return result !== null ? result <= 0 : null; + } +} + +export class InOperator extends BinaryOperator { + binary(evaluator: Evaluator, haystack: unknown, needle: unknown): boolean | null { + if (Array.isArray(haystack)) { + for (const item of haystack) { + if (evaluator.compare(item, needle) === 0) return true; + } + return false; + } else if (typeof haystack === "string") { + const needleString = evaluator.stringConvert(needle); + return needleString !== null && haystack.includes(needleString); + } else if (isObject(haystack)) { + const needleString = evaluator.stringConvert(needle); + return needleString != null && Object.prototype.hasOwnProperty.call(haystack, needleString); + } + return null; + } +} + +export class MatchOperator extends BinaryOperator { + binary(evaluator: Evaluator, text: unknown, pattern: unknown): boolean | null { + const textStr = evaluator.stringConvert(text); + if (textStr !== null) { + const patternStr = evaluator.stringConvert(pattern); + if (patternStr !== null) { + try { + return new RegExp(patternStr).test(textStr); + } catch { + return null; + } + } + } + return null; + } +} + +export class SemverEqualsOperator extends BinaryOperator { + binary(evaluator: Evaluator, lhs: unknown, rhs: unknown): boolean | null { + const result = evaluator.versionCompare(lhs, rhs); + return result !== null ? result === 0 : null; + } +} + +export class SemverGreaterThanOperator extends BinaryOperator { + binary(evaluator: Evaluator, lhs: unknown, rhs: unknown): boolean | null { + const result = evaluator.versionCompare(lhs, rhs); + return result !== null ? result > 0 : null; + } +} + +export class SemverGreaterThanOrEqualOperator extends BinaryOperator { + binary(evaluator: Evaluator, lhs: unknown, rhs: unknown): boolean | null { + const result = evaluator.versionCompare(lhs, rhs); + return result !== null ? result >= 0 : null; + } +} + +export class SemverLessThanOperator extends BinaryOperator { + binary(evaluator: Evaluator, lhs: unknown, rhs: unknown): boolean | null { + const result = evaluator.versionCompare(lhs, rhs); + return result !== null ? result < 0 : null; + } +} + +export class SemverLessThanOrEqualOperator extends BinaryOperator { + binary(evaluator: Evaluator, lhs: unknown, rhs: unknown): boolean | null { + const result = evaluator.versionCompare(lhs, rhs); + return result !== null ? result <= 0 : null; + } +} +``` + +- [ ] **Step 4: Run tests to verify they pass** + +Run: `npx vitest run src/__tests__/jsonexpr/operators.test.ts` + +Expected: All tests pass + +- [ ] **Step 5: Commit** + +```bash +git add src/jsonexpr/operators.ts src/__tests__/jsonexpr/operators.test.ts +git commit -m "feat: add all 20 JsonExpr operators" +``` + +--- + +## Task 10: JsonExpr Facade & AudienceMatcher + +**Files:** +- Create: `src/jsonexpr/jsonexpr.ts` +- Create: `src/matcher.ts` +- Create: `src/__tests__/jsonexpr/jsonexpr.test.ts` +- Create: `src/__tests__/matcher.test.ts` + +- [ ] **Step 1: Write the failing tests for JsonExpr** + +```typescript +import { describe, expect, test } from "vitest"; +import { JsonExpr } from "../../jsonexpr/jsonexpr"; + +describe("JsonExpr", () => { + const jsonExpr = new JsonExpr(); + + test("evaluateBooleanExpr with and-array", () => { + expect(jsonExpr.evaluateBooleanExpr([{ value: true }, { value: 1 }], {})).toBe(true); + expect(jsonExpr.evaluateBooleanExpr([{ value: true }, { value: false }], {})).toBe(false); + }); + + test("evaluateBooleanExpr with object expr", () => { + expect(jsonExpr.evaluateBooleanExpr({ value: true }, {})).toBe(true); + expect(jsonExpr.evaluateBooleanExpr({ value: false }, {})).toBe(false); + }); + + test("evaluateExpr returns raw value", () => { + expect(jsonExpr.evaluateExpr({ value: 42 }, {})).toBe(42); + expect(jsonExpr.evaluateExpr({ value: "hello" }, {})).toBe("hello"); + }); + + test("var operator extracts from vars", () => { + expect(jsonExpr.evaluateExpr({ var: "name" }, { name: "Alice" })).toBe("Alice"); + }); + + test("complex expression with eq and var", () => { + const expr = { eq: [{ var: "age" }, { value: 25 }] }; + expect(jsonExpr.evaluateBooleanExpr(expr, { age: 25 })).toBe(true); + expect(jsonExpr.evaluateBooleanExpr(expr, { age: 30 })).toBe(false); + }); +}); +``` + +- [ ] **Step 2: Write the failing tests for AudienceMatcher** + +```typescript +import { describe, expect, test } from "vitest"; +import { AudienceMatcher } from "../matcher"; + +describe("AudienceMatcher", () => { + const matcher = new AudienceMatcher(); + + test("evaluates matching audience", () => { + const audience = JSON.stringify({ filter: [{ value: true }] }); + expect(matcher.evaluate(audience, {})).toBe(true); + }); + + test("evaluates non-matching audience", () => { + const audience = JSON.stringify({ filter: [{ value: false }] }); + expect(matcher.evaluate(audience, {})).toBe(false); + }); + + test("evaluates with not operator", () => { + const audience = JSON.stringify({ filter: [{ not: { value: false } }] }); + expect(matcher.evaluate(audience, {})).toBe(true); + }); + + test("returns null for invalid JSON", () => { + expect(matcher.evaluate("invalid json", {})).toBe(null); + }); + + test("returns null for missing filter", () => { + expect(matcher.evaluate(JSON.stringify({}), {})).toBe(null); + }); + + test("returns null for null filter", () => { + expect(matcher.evaluate(JSON.stringify({ filter: null }), {})).toBe(null); + }); +}); +``` + +- [ ] **Step 3: Run tests to verify they fail** + +Run: `npx vitest run src/__tests__/jsonexpr/jsonexpr.test.ts src/__tests__/matcher.test.ts` + +Expected: FAIL - modules not found + +- [ ] **Step 4: Write src/jsonexpr/jsonexpr.ts** + +```typescript +import { Evaluator } from "./evaluator"; +import { + AndCombinator, + EqualsOperator, + GreaterThanOperator, + GreaterThanOrEqualOperator, + InOperator, + LessThanOperator, + LessThanOrEqualOperator, + MatchOperator, + NotOperator, + NullOperator, + OrCombinator, + SemverEqualsOperator, + SemverGreaterThanOperator, + SemverGreaterThanOrEqualOperator, + SemverLessThanOperator, + SemverLessThanOrEqualOperator, + ValueOperator, + VarOperator, +} from "./operators"; + +const operators = { + and: new AndCombinator(), + or: new OrCombinator(), + value: new ValueOperator(), + var: new VarOperator(), + null: new NullOperator(), + not: new NotOperator(), + in: new InOperator(), + match: new MatchOperator(), + eq: new EqualsOperator(), + gt: new GreaterThanOperator(), + gte: new GreaterThanOrEqualOperator(), + lt: new LessThanOperator(), + lte: new LessThanOrEqualOperator(), + semver_eq: new SemverEqualsOperator(), + semver_gt: new SemverGreaterThanOperator(), + semver_gte: new SemverGreaterThanOrEqualOperator(), + semver_lt: new SemverLessThanOperator(), + semver_lte: new SemverLessThanOrEqualOperator(), +}; + +export class JsonExpr { + evaluateBooleanExpr(expr: unknown, vars: Record): boolean { + const evaluator = new Evaluator(operators, vars); + return evaluator.booleanConvert(evaluator.evaluate(expr)); + } + + evaluateExpr(expr: unknown, vars: Record): unknown { + const evaluator = new Evaluator(operators, vars); + return evaluator.evaluate(expr); + } +} +``` + +- [ ] **Step 5: Write src/matcher.ts** + +```typescript +import { isObject } from "./utils"; +import { JsonExpr } from "./jsonexpr/jsonexpr"; + +export class AudienceMatcher { + private readonly _jsonExpr = new JsonExpr(); + + evaluate(audienceString: string, vars: Record): boolean | null { + try { + const audience = JSON.parse(audienceString); + if (audience && audience.filter) { + if (Array.isArray(audience.filter) || isObject(audience.filter)) { + return this._jsonExpr.evaluateBooleanExpr(audience.filter, vars); + } + } + } catch { + // invalid JSON + } + return null; + } +} +``` + +- [ ] **Step 6: Run tests to verify they pass** + +Run: `npx vitest run src/__tests__/jsonexpr/jsonexpr.test.ts src/__tests__/matcher.test.ts` + +Expected: All tests pass + +- [ ] **Step 7: Commit** + +```bash +git add src/jsonexpr/jsonexpr.ts src/matcher.ts src/__tests__/jsonexpr/jsonexpr.test.ts src/__tests__/matcher.test.ts +git commit -m "feat: add JsonExpr facade and AudienceMatcher" +``` + +--- + +## Task 11: HTTP Client + +**Files:** +- Create: `src/client.ts` +- Create: `src/__tests__/client.test.ts` + +- [ ] **Step 1: Write the failing tests** + +```typescript +import { describe, expect, test, vi, beforeEach, afterEach } from "vitest"; +import { Client } from "../client"; +import type { ClientOptions } from "../types"; + +const defaultOpts: ClientOptions = { + agent: "test-agent", + apiKey: "test-api-key", + application: "test-app", + endpoint: "https://test.absmartly.io/v1", + environment: "test", +}; + +describe("Client", () => { + describe("constructor validation", () => { + test("throws for missing apiKey", () => { + const opts = { ...defaultOpts, apiKey: undefined } as unknown as ClientOptions; + expect(() => new Client(opts)).toThrow("Missing 'apiKey' in options argument"); + }); + + test("throws for missing endpoint", () => { + const opts = { ...defaultOpts, endpoint: undefined } as unknown as ClientOptions; + expect(() => new Client(opts)).toThrow("Missing 'endpoint' in options argument"); + }); + + test("throws for missing environment", () => { + const opts = { ...defaultOpts, environment: undefined } as unknown as ClientOptions; + expect(() => new Client(opts)).toThrow("Missing 'environment' in options argument"); + }); + + test("throws for missing application", () => { + const opts = { ...defaultOpts, application: undefined } as unknown as ClientOptions; + expect(() => new Client(opts)).toThrow("Missing 'application' in options argument"); + }); + + test("throws for empty apiKey", () => { + const opts = { ...defaultOpts, apiKey: "" }; + expect(() => new Client(opts)).toThrow("Invalid 'apiKey' in options argument"); + }); + + test("accepts ApplicationObject", () => { + const opts = { ...defaultOpts, application: { name: "my-app", version: "1.0.0" } }; + const client = new Client(opts); + expect(client.getApplication()).toEqual({ name: "my-app", version: "1.0.0" }); + }); + + test("converts string application to ApplicationObject", () => { + const client = new Client(defaultOpts); + expect(client.getApplication()).toEqual({ name: "test-app", version: 0 }); + }); + }); + + describe("accessors", () => { + test("getAgent", () => { + const client = new Client(defaultOpts); + expect(client.getAgent()).toBe("test-agent"); + }); + + test("getEnvironment", () => { + const client = new Client(defaultOpts); + expect(client.getEnvironment()).toBe("test"); + }); + }); + + describe("getContext", () => { + beforeEach(() => { + vi.stubGlobal("fetch", vi.fn()); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + test("makes GET request to /context", async () => { + const mockResponse = { ok: true, json: () => Promise.resolve({ experiments: [] }) }; + (globalThis.fetch as ReturnType).mockResolvedValue(mockResponse); + + const client = new Client({ ...defaultOpts, retries: 0, timeout: 1000 }); + await client.getContext(); + + expect(globalThis.fetch).toHaveBeenCalledTimes(1); + const [url] = (globalThis.fetch as ReturnType).mock.calls[0]!; + expect(url).toContain("/context"); + expect(url).toContain("application=test-app"); + expect(url).toContain("environment=test"); + }); + }); + + describe("publish", () => { + beforeEach(() => { + vi.stubGlobal("fetch", vi.fn()); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + test("makes PUT request to /context with auth headers", async () => { + const mockResponse = { ok: true, json: () => Promise.resolve({}) }; + (globalThis.fetch as ReturnType).mockResolvedValue(mockResponse); + + const client = new Client({ ...defaultOpts, retries: 0, timeout: 1000 }); + await client.publish({ + units: [{ type: "session_id", uid: "abc" }], + publishedAt: 1000, + hashed: true, + sdkVersion: "2.0.0", + }); + + const [, opts] = (globalThis.fetch as ReturnType).mock.calls[0]!; + expect(opts.method).toBe("PUT"); + expect(opts.headers["X-API-Key"]).toBe("test-api-key"); + expect(opts.headers["X-Agent"]).toBe("test-agent"); + expect(opts.headers["X-Environment"]).toBe("test"); + }); + + test("omits empty goals and exposures arrays", async () => { + const mockResponse = { ok: true, json: () => Promise.resolve({}) }; + (globalThis.fetch as ReturnType).mockResolvedValue(mockResponse); + + const client = new Client({ ...defaultOpts, retries: 0, timeout: 1000 }); + await client.publish({ + units: [{ type: "session_id", uid: "abc" }], + publishedAt: 1000, + hashed: true, + sdkVersion: "2.0.0", + goals: [], + exposures: [], + }); + + const [, opts] = (globalThis.fetch as ReturnType).mock.calls[0]!; + const body = JSON.parse(opts.body); + expect(body.goals).toBeUndefined(); + expect(body.exposures).toBeUndefined(); + }); + }); + + describe("retry logic", () => { + beforeEach(() => { + vi.useFakeTimers(); + vi.stubGlobal("fetch", vi.fn()); + }); + + afterEach(() => { + vi.useRealTimers(); + vi.restoreAllMocks(); + }); + + test("retries on server error", async () => { + const failResponse = { ok: false, status: 500, statusText: "Server Error", text: () => Promise.resolve("") }; + const successResponse = { ok: true, json: () => Promise.resolve({ data: "ok" }) }; + + (globalThis.fetch as ReturnType) + .mockResolvedValueOnce(failResponse) + .mockResolvedValueOnce(successResponse); + + const client = new Client({ ...defaultOpts, retries: 3, timeout: 10000 }); + const promise = client.getContext(); + + await vi.runAllTimersAsync(); + const result = await promise; + + expect(result).toEqual({ data: "ok" }); + expect(globalThis.fetch).toHaveBeenCalledTimes(2); + }); + + test("does not retry on 4xx error", async () => { + const failResponse = { + ok: false, + status: 400, + statusText: "Bad Request", + text: () => Promise.resolve("bad request"), + }; + + (globalThis.fetch as ReturnType).mockResolvedValue(failResponse); + + const client = new Client({ ...defaultOpts, retries: 3, timeout: 10000 }); + const promise = client.getContext(); + + await vi.runAllTimersAsync(); + await expect(promise).rejects.toThrow("bad request"); + expect(globalThis.fetch).toHaveBeenCalledTimes(1); + }); + }); +}); +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `npx vitest run src/__tests__/client.test.ts` + +Expected: FAIL - module not found + +- [ ] **Step 3: Write src/client.ts** + +```typescript +import { AbortError, RetryError, TimeoutError } from "./errors"; +import type { + ApplicationObject, + ClientOptions, + ClientRequestOptions, + ContextOptions, + ContextParams, + FetchResponse, + NormalizedClientOptions, + PublishParams, +} from "./types"; + +export class Client { + private readonly _opts: NormalizedClientOptions; + private readonly _delay: number; + + constructor(opts: ClientOptions) { + const merged: Record = Object.assign( + { agent: "javascript-client", retries: 5, timeout: 3000, keepalive: true }, + opts, + ); + + for (const key of ["agent", "application", "apiKey", "endpoint", "environment"]) { + if (key in merged && merged[key] !== undefined) { + const value = merged[key]; + if (typeof value !== "string" || (value as string).length === 0) { + if (key === "application") { + if (value !== null && typeof value === "object" && "name" in (value as object)) continue; + } + throw new Error(`Invalid '${key}' in options argument`); + } + } else { + throw new Error(`Missing '${key}' in options argument`); + } + } + + if (typeof merged.application === "string") { + merged.application = { name: merged.application, version: 0 }; + } + + this._opts = merged as unknown as NormalizedClientOptions; + this._delay = 50; + } + + getContext(options?: Partial): Promise { + return this.getUnauthed({ + ...options, + path: "/context", + query: { + application: this._opts.application.name, + environment: this._opts.environment, + }, + }); + } + + createContext(params: ContextParams, options: ContextOptions): Promise { + return this.post({ ...options, path: "/context", body: { units: params.units } }); + } + + publish(params: PublishParams, options?: ClientRequestOptions): Promise { + const body: Record = { + units: params.units, + hashed: params.hashed, + publishedAt: params.publishedAt || Date.now(), + sdkVersion: params.sdkVersion, + }; + + if (Array.isArray(params.goals) && params.goals.length > 0) { + body.goals = params.goals; + } + if (Array.isArray(params.exposures) && params.exposures.length > 0) { + body.exposures = params.exposures; + } + if (Array.isArray(params.attributes) && params.attributes.length > 0) { + body.attributes = params.attributes; + } + + return this.put({ ...options, path: "/context", body }); + } + + request(options: ClientRequestOptions): Promise { + let url = `${this._opts.endpoint}${options.path}`; + if (options.query) { + const keys = Object.keys(options.query); + if (keys.length > 0) { + const encoded = keys + .map((k) => (options.query ? `${k}=${encodeURIComponent(options.query[k]!)}` : null)) + .join("&"); + url = `${url}?${encoded}`; + } + } + + const controller = new AbortController(); + + const tryOnce = (): Promise => { + const opts: RequestInit = { + method: options.method, + body: options.body !== undefined ? JSON.stringify(options.body, null, 0) : undefined, + signal: controller.signal, + keepalive: this._opts.keepalive, + }; + + if (options.auth) { + opts.headers = { + "Content-Type": "application/json", + "X-API-Key": this._opts.apiKey, + "X-Agent": this._opts.agent, + "X-Environment": this._opts.environment, + "X-Application": this._opts.application.name, + "X-Application-Version": String(this._opts.application.version), + }; + } + + return fetch(url, opts).then((response: Response) => { + if (!response.ok) { + const bail = response.status >= 400 && response.status < 500; + return response.text().then((text: string) => { + const error: Error & { _bail?: boolean } = new Error( + text !== null && text.length > 0 ? text : response.statusText, + ); + error._bail = bail; + return Promise.reject(error); + }); + } + return response.json(); + }); + }; + + type WaitFn = ((ms: number) => Promise) & { reject?: (reason: AbortError) => void }; + type TryWithFn = ((retries: number, timeout: number, tries?: number, waited?: number) => Promise) & { + timedout?: boolean; + }; + + const wait: WaitFn = (ms) => + new Promise((resolve, reject) => { + const timeoutId = setTimeout(() => { + delete wait.reject; + resolve(); + }, ms); + + wait.reject = (reason) => { + clearTimeout(timeoutId); + reject(reason); + }; + }); + + const tryWith: TryWithFn = (retries, timeout, tries = 0, waited = 0) => { + delete tryWith.timedout; + + return tryOnce().catch((reason: Error & { _bail?: boolean }) => { + if (reason._bail || retries <= 0) throw new Error(reason.message); + if (tries >= retries) throw new RetryError(tries, reason, url); + if (waited >= timeout || reason.name === "AbortError") { + if (tryWith.timedout) throw new TimeoutError(timeout); + throw reason; + } + + let delay = (1 << tries) * this._delay + 0.5 * Math.random() * this._delay; + if (waited + delay > timeout) delay = timeout - waited; + + return wait(delay).then(() => tryWith(retries, timeout, tries + 1, waited + delay)); + }); + }; + + const abort = () => { + if (wait.reject) { + wait.reject(new AbortError()); + } else { + controller.abort(); + } + }; + + if (options.signal) { + options.signal.addEventListener("abort", abort); + } + + const timeout = options.timeout || this._opts.timeout || 0; + const timeoutId = + timeout > 0 + ? setTimeout(() => { + tryWith.timedout = true; + abort(); + }, timeout) + : 0; + + const finalCleanUp = () => { + clearTimeout(timeoutId); + if (options.signal) { + options.signal.removeEventListener("abort", abort); + } + }; + + return tryWith(this._opts.retries ?? 5, this._opts.timeout ?? 3000) + .then((value) => { + finalCleanUp(); + return value; + }) + .catch((error: Error) => { + finalCleanUp(); + throw error; + }); + } + + post(options: ClientRequestOptions): Promise { + return this.request({ ...options, auth: true, method: "POST" }); + } + + put(options: ClientRequestOptions): Promise { + return this.request({ ...options, auth: true, method: "PUT" }); + } + + getAgent(): string { + return this._opts.agent; + } + + getApplication(): ApplicationObject { + return this._opts.application; + } + + getEnvironment(): string { + return this._opts.environment; + } + + getUnauthed(options: ClientRequestOptions): Promise { + return this.request({ ...options, method: "GET" }); + } +} +``` + +- [ ] **Step 4: Run tests to verify they pass** + +Run: `npx vitest run src/__tests__/client.test.ts` + +Expected: All tests pass + +- [ ] **Step 5: Commit** + +```bash +git add src/client.ts src/__tests__/client.test.ts +git commit -m "feat: add HTTP client with retry logic and exponential backoff" +``` + +--- + +## Task 12: Provider & Publisher + +**Files:** +- Create: `src/provider.ts` +- Create: `src/publisher.ts` +- Create: `src/__tests__/provider.test.ts` +- Create: `src/__tests__/publisher.test.ts` + +- [ ] **Step 1: Write the failing tests for provider** + +```typescript +import { describe, expect, test, vi } from "vitest"; +import { ContextDataProvider } from "../provider"; + +describe("ContextDataProvider", () => { + test("delegates to sdk.getClient().getContext()", async () => { + const mockGetContext = vi.fn().mockResolvedValue({ experiments: [] }); + const mockSdk = { getClient: () => ({ getContext: mockGetContext }) }; + + const provider = new ContextDataProvider(); + const result = await provider.getContextData(mockSdk, { path: "/test" }); + + expect(mockGetContext).toHaveBeenCalledWith({ path: "/test" }); + expect(result).toEqual({ experiments: [] }); + }); +}); +``` + +- [ ] **Step 2: Write the failing tests for publisher** + +```typescript +import { describe, expect, test, vi } from "vitest"; +import { ContextPublisher } from "../publisher"; + +describe("ContextPublisher", () => { + test("delegates to sdk.getClient().publish()", async () => { + const mockPublish = vi.fn().mockResolvedValue({}); + const mockSdk = { getClient: () => ({ publish: mockPublish }) }; + const request = { + units: [{ type: "session_id", uid: "abc" }], + publishedAt: 1000, + hashed: true, + sdkVersion: "2.0.0", + }; + + const publisher = new ContextPublisher(); + await publisher.publish(request, mockSdk, {}, { path: "/test" }); + + expect(mockPublish).toHaveBeenCalledWith(request, { path: "/test" }); + }); +}); +``` + +- [ ] **Step 3: Run tests to verify they fail** + +Run: `npx vitest run src/__tests__/provider.test.ts src/__tests__/publisher.test.ts` + +Expected: FAIL - modules not found + +- [ ] **Step 4: Write src/provider.ts** + +```typescript +import type { ClientRequestOptions, ContextData } from "./types"; + +interface SDKLike { + getClient(): { getContext(options?: Partial): Promise }; +} + +export class ContextDataProvider { + getContextData(sdk: SDKLike, requestOptions?: Partial): Promise { + return sdk.getClient().getContext(requestOptions) as Promise; + } +} +``` + +- [ ] **Step 5: Write src/publisher.ts** + +```typescript +import type { ClientRequestOptions, PublishParams } from "./types"; + +interface SDKLike { + getClient(): { publish(request: PublishParams, options?: ClientRequestOptions): Promise }; +} + +export class ContextPublisher { + publish(request: PublishParams, sdk: SDKLike, _context: unknown, requestOptions?: ClientRequestOptions): Promise { + return sdk.getClient().publish(request, requestOptions); + } +} +``` + +- [ ] **Step 6: Run tests to verify they pass** + +Run: `npx vitest run src/__tests__/provider.test.ts src/__tests__/publisher.test.ts` + +Expected: All tests pass + +- [ ] **Step 7: Commit** + +```bash +git add src/provider.ts src/publisher.ts src/__tests__/provider.test.ts src/__tests__/publisher.test.ts +git commit -m "feat: add ContextDataProvider and ContextPublisher" +``` + +--- + +## Task 13: Context Class + +This is the largest and most complex module. The `Context` class manages experiment assignments, exposure tracking, goal tracking, attribute management, and publish/refresh lifecycle. + +**Files:** +- Create: `src/context.ts` +- Create: `src/__tests__/context.test.ts` + +- [ ] **Step 1: Write the failing tests** + +Write comprehensive tests covering all Context functionality. Due to the size of this module, the tests are extensive. The test should cover: + +1. Constructor with immediate data vs promise data +2. `isReady()`, `isFailed()`, `isFinalized()`, `isFinalizing()` state checks +3. `ready()` promise resolution +4. `unit()` / `units()` - setting and validating unit identifiers +5. `attribute()` / `attributes()` - setting user attributes +6. `treatment()` - variant assignment with exposure tracking +7. `peek()` - variant assignment without exposure +8. `track()` - goal tracking +9. `publish()` - flushing pending events +10. `refresh()` - fetching updated experiment data +11. `finalize()` - cleanup and final publish +12. `variableValue()` / `peekVariableValue()` - experiment variable access +13. `override()` / `overrides()` - forced variant assignment +14. `customAssignment()` / `customAssignments()` - custom assignment logic +15. `customFieldValue()` / `customFieldKeys()` - custom field access +16. Auto-publish timeout behavior +17. Refresh interval behavior +18. Error handling and failed state +19. System attributes inclusion + +The test file should use the same experiment fixture data from the original test suite to ensure byte-level compatibility. Create the file with the complete test content from the original `context.test.js` adapted to Vitest syntax (replace `jest.fn()` with `vi.fn()`, `jest.spyOn` with `vi.spyOn`, etc.). + +Run: `npx vitest run src/__tests__/context.test.ts` + +Expected: FAIL - module not found + +- [ ] **Step 2: Write src/context.ts** + +Port the Context class from the original codebase with strict TypeScript types. The implementation must: + +- Import types from `./types` instead of declaring locally +- Import `hashUnit` from `./hashing` instead of `./utils` +- Import `VariantAssigner` from `./assigner` +- Import `AudienceMatcher` from `./matcher` +- Import `insertUniqueSorted` from `./algorithm` +- Import `arrayEqualsShallow`, `isObject`, `isPromise` from `./utils` +- Import `SDK_VERSION` from `./version` +- Use native `setTimeout`/`setInterval`/`clearTimeout`/`clearInterval` +- Use `structuredClone` in `_buildAttributes` if cloning is needed +- Maintain identical API: all public methods have the same signatures +- All private methods prefixed with `_` as in original + +The logic must be identical to the original `context.ts` to maintain backward compatibility. + +- [ ] **Step 3: Run tests to verify they pass** + +Run: `npx vitest run src/__tests__/context.test.ts` + +Expected: All tests pass + +- [ ] **Step 4: Commit** + +```bash +git add src/context.ts src/__tests__/context.test.ts +git commit -m "feat: add Context class with experiment assignment and lifecycle management" +``` + +--- + +## Task 14: SDK Class + +**Files:** +- Create: `src/sdk.ts` +- Create: `src/__tests__/sdk.test.ts` + +- [ ] **Step 1: Write the failing tests** + +```typescript +import { describe, expect, test, vi } from "vitest"; +import { SDK } from "../sdk"; +import type { ClientOptions, ContextParams } from "../types"; + +const defaultOpts: ClientOptions = { + agent: "test-agent", + apiKey: "test-api-key", + application: "test-app", + endpoint: "https://test.absmartly.io/v1", + environment: "test", +}; + +describe("SDK", () => { + test("creates SDK instance", () => { + const sdk = new SDK(defaultOpts); + expect(sdk).toBeInstanceOf(SDK); + }); + + test("getClient returns client", () => { + const sdk = new SDK(defaultOpts); + expect(sdk.getClient()).toBeDefined(); + }); + + test("get/set event logger", () => { + const sdk = new SDK(defaultOpts); + const logger = vi.fn(); + sdk.setEventLogger(logger); + expect(sdk.getEventLogger()).toBe(logger); + }); + + test("default event logger logs errors", () => { + const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + const error = new Error("test"); + SDK.defaultEventLogger({} as never, "error", error); + expect(consoleSpy).toHaveBeenCalledWith(error); + consoleSpy.mockRestore(); + }); + + test("createContext validates unit types", () => { + const sdk = new SDK(defaultOpts); + expect(() => sdk.createContext({ units: { session_id: true as unknown as string } })).toThrow( + "Unit 'session_id' UID is of unsupported type 'boolean'. UID must be one of ['string', 'number']", + ); + }); + + test("createContext validates empty string units", () => { + const sdk = new SDK(defaultOpts); + expect(() => sdk.createContext({ units: { session_id: "" } })).toThrow( + "Unit 'session_id' UID length must be >= 1", + ); + }); + + test("createContext returns Context instance", () => { + vi.stubGlobal("fetch", vi.fn().mockResolvedValue({ ok: true, json: () => Promise.resolve({ experiments: [] }) })); + const sdk = new SDK(defaultOpts); + const context = sdk.createContext({ units: { session_id: "abc" } }); + expect(context).toBeDefined(); + vi.restoreAllMocks(); + }); + + test("createContextWith accepts pre-fetched data", () => { + const sdk = new SDK(defaultOpts); + const context = sdk.createContextWith({ units: { session_id: "abc" } }, { experiments: [] }); + expect(context).toBeDefined(); + expect(context.isReady()).toBe(true); + }); + + test("get/set context publisher", () => { + const sdk = new SDK(defaultOpts); + const publisher = { publish: vi.fn() }; + sdk.setContextPublisher(publisher as never); + expect(sdk.getContextPublisher()).toBe(publisher); + }); + + test("get/set context data provider", () => { + const sdk = new SDK(defaultOpts); + const provider = { getContextData: vi.fn() }; + sdk.setContextDataProvider(provider as never); + expect(sdk.getContextDataProvider()).toBe(provider); + }); +}); +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `npx vitest run src/__tests__/sdk.test.ts` + +Expected: FAIL - module not found + +- [ ] **Step 3: Write src/sdk.ts** + +```typescript +import { Client } from "./client"; +import { Context } from "./context"; +import { ContextPublisher } from "./publisher"; +import { ContextDataProvider } from "./provider"; +import type { + ClientOptions, + ClientRequestOptions, + ContextData, + ContextOptions, + ContextParams, + EventLogger, + EventLoggerData, + EventName, + Exposure, + Goal, + PublishParams, + SDKOptions, +} from "./types"; + +function isLongLivedApp(): boolean { + return (typeof window !== "undefined" && typeof window.document !== "undefined") || + (typeof navigator !== "undefined" && navigator.product === "ReactNative"); +} + +const CLIENT_OPTION_KEYS = ["application", "agent", "apiKey", "endpoint", "keepalive", "environment", "retries", "timeout"]; + +export class SDK { + static defaultEventLogger: EventLogger = (_, eventName, data) => { + if (eventName === "error") console.error(data); + }; + + private _eventLogger: EventLogger; + private _publisher: ContextPublisher; + private _provider: ContextDataProvider; + private readonly _client: Client; + + constructor(options: ClientOptions & SDKOptions) { + const clientOptions = Object.assign( + { agent: "absmartly-javascript-sdk" }, + ...Object.entries(options || {}) + .filter((x) => CLIENT_OPTION_KEYS.indexOf(x[0]) !== -1) + .map((x) => ({ [x[0]]: x[1] })), + ) as ClientOptions; + + this._client = (options.client as Client) || new Client(clientOptions); + this._eventLogger = options.eventLogger || SDK.defaultEventLogger; + this._publisher = (options.publisher as ContextPublisher) || new ContextPublisher(); + this._provider = (options.provider as ContextDataProvider) || new ContextDataProvider(); + } + + getContextData(requestOptions: ClientRequestOptions): Promise { + return this._provider.getContextData(this, requestOptions); + } + + createContext( + params: ContextParams, + options?: Partial, + requestOptions?: Partial, + ): Context { + SDK._validateParams(params); + const fullOptions = SDK._contextOptions(options); + const data = this._provider.getContextData(this, requestOptions); + return new Context(this, fullOptions, params, data); + } + + createContextWith( + params: ContextParams, + data: ContextData | Promise, + options?: Partial, + ): Context { + SDK._validateParams(params); + const fullOptions = SDK._contextOptions(options); + return new Context(this, fullOptions, params, data); + } + + setEventLogger(logger: EventLogger): void { + this._eventLogger = logger; + } + + getEventLogger(): EventLogger { + return this._eventLogger; + } + + setContextPublisher(publisher: ContextPublisher): void { + this._publisher = publisher; + } + + getContextPublisher(): ContextPublisher { + return this._publisher; + } + + setContextDataProvider(provider: ContextDataProvider): void { + this._provider = provider; + } + + getContextDataProvider(): ContextDataProvider { + return this._provider; + } + + getClient(): Client { + return this._client; + } + + private static _contextOptions(options?: Partial): ContextOptions { + return Object.assign( + { publishDelay: isLongLivedApp() ? 100 : -1, refreshPeriod: 0 }, + options || {}, + ) as ContextOptions; + } + + private static _validateParams(params: ContextParams): void { + for (const [key, value] of Object.entries(params.units)) { + const type = typeof value; + if (type !== "string" && type !== "number") { + throw new Error(`Unit '${key}' UID is of unsupported type '${type}'. UID must be one of ['string', 'number']`); + } + if (typeof value === "string" && value.length === 0) { + throw new Error(`Unit '${key}' UID length must be >= 1`); + } + } + } +} +``` + +- [ ] **Step 4: Run tests to verify they pass** + +Run: `npx vitest run src/__tests__/sdk.test.ts` + +Expected: All tests pass + +- [ ] **Step 5: Commit** + +```bash +git add src/sdk.ts src/__tests__/sdk.test.ts +git commit -m "feat: add SDK class with context creation and configuration" +``` + +--- + +## Task 15: Config Merge Utility + +**Files:** +- Create: `src/config.ts` +- Create: `src/__tests__/config.test.ts` + +- [ ] **Step 1: Write the failing tests** + +```typescript +import { describe, expect, test, vi } from "vitest"; +import { mergeConfig } from "../config"; + +function mockContext(variableKeys: Record, variableValues: Record = {}) { + return { + variableKeys: () => variableKeys, + variableValue: (key: string, defaultValue: unknown) => + key in variableValues ? variableValues[key] : defaultValue, + }; +} + +describe("mergeConfig", () => { + test("returns new object, does not mutate original", () => { + const original = { key: "value" }; + const context = mockContext({}); + const result = mergeConfig(context as never, original); + expect(result).not.toBe(original); + expect(result).toEqual({ key: "value" }); + }); + + test("creates getter for experiment variable", () => { + const context = mockContext( + { "button.color": ["exp_test"] }, + { "button.color": "red" }, + ); + const config = { button: { color: "blue" } }; + const result = mergeConfig(context as never, config); + expect(result.button.color).toBe("red"); + }); + + test("falls back to default when variable not set", () => { + const context = mockContext({ "button.color": ["exp_test"] }); + const config = { button: { color: "blue" } }; + const result = mergeConfig(context as never, config); + expect(result.button.color).toBe("blue"); + }); +}); +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `npx vitest run src/__tests__/config.test.ts` + +Expected: FAIL - module not found + +- [ ] **Step 3: Write src/config.ts** + +```typescript +import { isObject } from "./utils"; + +interface ConfigContext { + variableKeys(): Record; + variableValue(key: string, defaultValue: unknown): unknown; +} + +export function mergeConfig(context: ConfigContext, previousConfig: Record): Record { + const merged = structuredClone(previousConfig); + const keys = context.variableKeys(); + + for (const [variableKey, experimentName] of Object.entries(keys)) { + let target: Record | undefined = merged; + const frags = variableKey.split("."); + + for (let index = 0; index < frags.length; ++index) { + const frag = frags[index]!; + + if (target === undefined) break; + + if (`_${frag}_setter` in target) { + console.error( + `Config key '${frags.slice(0, index + 1).join(".")}' already set by experiment '${target[`_${frag}_setter`]}'.`, + ); + target = undefined; + break; + } + + if (frag in target) { + if (index < frags.length - 1) { + if (!isObject(target[frag])) { + console.warn( + `Config key '${variableKey}' for experiment '${experimentName}' is overriding non-object value at '${frags.slice(0, index + 1).join(".")}' with an object.`, + ); + target[frag] = {}; + target = target[frag] as Record; + } else { + target = target[frag] as Record; + } + } + } + + if (index === frags.length - 1) { + const defaultValue = target[frag]; + + Object.defineProperty(target, `_${frag}_setter`, { value: experimentName, writable: false }); + Object.defineProperty(target, frag, { + get: () => context.variableValue(variableKey, defaultValue), + }); + } + } + } + + return merged; +} +``` + +- [ ] **Step 4: Run tests to verify they pass** + +Run: `npx vitest run src/__tests__/config.test.ts` + +Expected: All tests pass + +- [ ] **Step 5: Commit** + +```bash +git add src/config.ts src/__tests__/config.test.ts +git commit -m "feat: add mergeConfig utility for experiment variable injection" +``` + +--- + +## Task 16: Public API & Index + +**Files:** +- Create: `src/index.ts` +- Remove: `src/__tests__/placeholder.test.ts` + +- [ ] **Step 1: Write src/index.ts** + +```typescript +export { SDK } from "./sdk"; +export { Context } from "./context"; +export { ContextDataProvider } from "./provider"; +export { ContextPublisher } from "./publisher"; +export { mergeConfig } from "./config"; + +export type { + ApplicationObject, + Attribute, + Assignment, + ClientOptions, + ClientRequestOptions, + ContextData, + ContextOptions, + ContextParams, + CustomFieldValue, + CustomFieldValueType, + EventLogger, + EventLoggerData, + EventName, + Experiment, + ExperimentData, + Exposure, + Goal, + JSONValue, + NormalizedClientOptions, + PublishParams, + SDKOptions, + Unit, + Units, +} from "./types"; +``` + +- [ ] **Step 2: Remove placeholder test** + +```bash +rm -f src/__tests__/placeholder.test.ts +``` + +- [ ] **Step 3: Verify full build** + +```bash +npx tsc --noEmit && npx tsup +``` + +Expected: Build succeeds, `dist/` contains all output files with correct exports + +- [ ] **Step 4: Verify all tests pass** + +```bash +npx vitest run +``` + +Expected: All tests across all modules pass + +- [ ] **Step 5: Commit** + +```bash +git add -A +git commit -m "feat: add public API exports and verify complete build" +``` + +--- + +## Task 17: Full Integration & Cleanup + +**Files:** +- Modify: `package.json` (if needed) +- Remove: any leftover old files + +- [ ] **Step 1: Clean up any remaining old files** + +```bash +rm -rf js/ lib/ es/ dist/ types/ +``` + +Verify no old config files remain. + +- [ ] **Step 2: Run full build pipeline** + +```bash +npm run build +``` + +Expected: `dist/` contains: +- `index.js` (ESM) +- `index.cjs` (CommonJS) +- `index.global.js` (browser IIFE) +- `index.d.ts` (TypeScript declarations) +- `index.d.cts` (CTS declarations) + +- [ ] **Step 3: Run full test suite with coverage** + +```bash +npm run test:coverage +``` + +Expected: All tests pass with high coverage + +- [ ] **Step 4: Verify TypeScript strict mode passes** + +```bash +npx tsc --noEmit +``` + +Expected: No type errors + +- [ ] **Step 5: Commit** + +```bash +git add -A +git commit -m "chore: final cleanup and verify full build pipeline" +``` diff --git a/scripts/generate-version.js b/scripts/generate-version.js deleted file mode 100644 index 8824f3c..0000000 --- a/scripts/generate-version.js +++ /dev/null @@ -1,7 +0,0 @@ -const fs = require("fs"); -const path = require("path"); - -const pkg = require(path.resolve(__dirname, "../package.json")); -const versionFile = path.resolve(__dirname, "../src/version.ts"); - -fs.writeFileSync(versionFile, `export const SDK_VERSION = "${pkg.version}";\n`); diff --git a/src/__tests__/abort-controller-shim.test.js b/src/__tests__/abort-controller-shim.test.js deleted file mode 100644 index fdc3ee0..0000000 --- a/src/__tests__/abort-controller-shim.test.js +++ /dev/null @@ -1,91 +0,0 @@ -// eslint-disable-next-line no-shadow -import { AbortController, AbortSignal } from "../abort-controller-shim"; - -describe("AbortSignal", () => { - const expectedEvent = expect.objectContaining({ - type: expect.any(String), - cancelable: false, - bubbles: false, - }); - - describe("dispatchEvent", () => { - it("calls listeners", async () => { - const aborter = new AbortController(); - const signal = aborter.signal; - const listener1 = jest.fn(); - const listener2 = jest.fn(); - const listener3 = jest.fn(); - signal.onabort = listener3; - signal.addEventListener("abort", listener1); - signal.addEventListener("abort", listener2); - signal.dispatchEvent({ type: "abort", cancelable: false, bubbles: false }); - - expect(listener1).toHaveBeenCalledTimes(1); - expect(listener1).toHaveBeenCalledWith(expectedEvent); - - expect(listener2).toHaveBeenCalledTimes(1); - expect(listener2).toHaveBeenCalledWith(expectedEvent); - - expect(listener3).toHaveBeenCalledTimes(1); - expect(listener3).toHaveBeenCalledWith(expectedEvent); - - signal.removeEventListener("abort", listener1); - signal.removeEventListener("abort", listener2); - - listener1.mockClear(); - listener2.mockClear(); - listener3.mockClear(); - - signal.dispatchEvent({ type: "abort", cancelable: false, bubbles: false }); - - expect(listener1).not.toHaveBeenCalled(); - expect(listener2).not.toHaveBeenCalled(); - expect(listener3).toHaveBeenCalledTimes(1); - }); - }); - - it("toString() returns [object AbortSignal]", async () => { - const aborter = new AbortSignal(); - - expect(aborter.toString()).toEqual("[object AbortSignal]"); - }); - - it("toStringTag is set to AbortSignal", async () => { - const aborter = new AbortSignal(); - - expect(aborter[Symbol.toStringTag]).toEqual("AbortSignal"); - }); -}); - -describe("AbortController", () => { - it("creates abort signal", async () => { - const aborter = new AbortController(); - expect(aborter.signal).toBeInstanceOf(AbortSignal); - expect(aborter.signal.aborted).toBe(false); - }); - - it("abort dispatches event on signal and aborted is set", async () => { - const aborter = new AbortController(); - jest.spyOn(aborter.signal, "dispatchEvent").mockImplementation(() => {}); - - aborter.abort(); - - expect(aborter.signal.aborted).toBe(true); - expect(aborter.signal.dispatchEvent).toHaveBeenCalledTimes(1); - expect(aborter.signal.dispatchEvent).toHaveBeenCalledWith( - expect.objectContaining({ type: expect.any(String) }) - ); - }); - - it("toString() returns [object AbortController]", async () => { - const aborter = new AbortController(); - - expect(aborter.toString()).toEqual("[object AbortController]"); - }); - - it("toStringTag is set to AbortController", async () => { - const aborter = new AbortController(); - - expect(aborter[Symbol.toStringTag]).toEqual("AbortController"); - }); -}); diff --git a/src/__tests__/algorithm.test.js b/src/__tests__/algorithm.test.js deleted file mode 100644 index f4ab39d..0000000 --- a/src/__tests__/algorithm.test.js +++ /dev/null @@ -1,43 +0,0 @@ -import { insertUniqueSorted } from "../algorithm"; - -describe("insertUniqueSorted", () => { - it("should insert a number into the center of an array", (done) => { - const arr = [0, 1, 3, 5, 8]; - - insertUniqueSorted(arr, 2, (a, b) => a < b); - - expect(arr).toEqual([0, 1, 2, 3, 5, 8]); - - done(); - }); - - it("should not insert a duplicate value", (done) => { - const arr = [0, 1, 2, 3]; - - insertUniqueSorted(arr, 2, (a, b) => a < b); - - expect(arr).toEqual([0, 1, 2, 3]); - - done(); - }); - - it("should insert the highest value at the end of an array", (done) => { - const arr = [0, 1, 2, 3]; - - insertUniqueSorted(arr, 100, (a, b) => a < b); - - expect(arr).toEqual([0, 1, 2, 3, 100]); - - done(); - }); - - it("should insert the lowest value at the beginning of an array", (done) => { - const arr = [1, 2, 3]; - - insertUniqueSorted(arr, 0, (a, b) => a < b); - - expect(arr).toEqual([0, 1, 2, 3]); - - done(); - }); -}); diff --git a/src/__tests__/assigner.test.js b/src/__tests__/assigner.test.js deleted file mode 100644 index bdd2b63..0000000 --- a/src/__tests__/assigner.test.js +++ /dev/null @@ -1,66 +0,0 @@ -import { VariantAssigner } from "../assigner"; -import { hashUnit } from "../utils"; - -describe("VariantAssigner", () => { - it("assign() should be deterministic", (done) => { - const testCases = { - "bleh@absmartly.com": [ - [[0.5, 0.5], 0x00000000, 0x00000000, 0], - [[0.5, 0.5], 0x00000000, 0x00000001, 1], - [[0.5, 0.5], 0x8015406f, 0x7ef49b98, 0], - [[0.5, 0.5], 0x3b2e7d90, 0xca87df4d, 0], - [[0.5, 0.5], 0x52c1f657, 0xd248bb2e, 0], - [[0.5, 0.5], 0x865a84d0, 0xaa22d41a, 0], - [[0.5, 0.5], 0x27d1dc86, 0x845461b9, 1], - [[0.33, 0.33, 0.34], 0x00000000, 0x00000000, 0], - [[0.33, 0.33, 0.34], 0x00000000, 0x00000001, 2], - [[0.33, 0.33, 0.34], 0x8015406f, 0x7ef49b98, 0], - [[0.33, 0.33, 0.34], 0x3b2e7d90, 0xca87df4d, 0], - [[0.33, 0.33, 0.34], 0x52c1f657, 0xd248bb2e, 0], - [[0.33, 0.33, 0.34], 0x865a84d0, 0xaa22d41a, 1], - [[0.33, 0.33, 0.34], 0x27d1dc86, 0x845461b9, 1], - ], - 123456789: [ - [[0.5, 0.5], 0x00000000, 0x00000000, 1], - [[0.5, 0.5], 0x00000000, 0x00000001, 0], - [[0.5, 0.5], 0x8015406f, 0x7ef49b98, 1], - [[0.5, 0.5], 0x3b2e7d90, 0xca87df4d, 1], - [[0.5, 0.5], 0x52c1f657, 0xd248bb2e, 1], - [[0.5, 0.5], 0x865a84d0, 0xaa22d41a, 0], - [[0.5, 0.5], 0x27d1dc86, 0x845461b9, 0], - [[0.33, 0.33, 0.34], 0x00000000, 0x00000000, 2], - [[0.33, 0.33, 0.34], 0x00000000, 0x00000001, 1], - [[0.33, 0.33, 0.34], 0x8015406f, 0x7ef49b98, 2], - [[0.33, 0.33, 0.34], 0x3b2e7d90, 0xca87df4d, 2], - [[0.33, 0.33, 0.34], 0x52c1f657, 0xd248bb2e, 2], - [[0.33, 0.33, 0.34], 0x865a84d0, 0xaa22d41a, 0], - [[0.33, 0.33, 0.34], 0x27d1dc86, 0x845461b9, 0], - ], - e791e240fcd3df7d238cfc285f475e8152fcc0ec: [ - [[0.5, 0.5], 0x00000000, 0x00000000, 1], - [[0.5, 0.5], 0x00000000, 0x00000001, 0], - [[0.5, 0.5], 0x8015406f, 0x7ef49b98, 1], - [[0.5, 0.5], 0x3b2e7d90, 0xca87df4d, 1], - [[0.5, 0.5], 0x52c1f657, 0xd248bb2e, 0], - [[0.5, 0.5], 0x865a84d0, 0xaa22d41a, 0], - [[0.5, 0.5], 0x27d1dc86, 0x845461b9, 0], - [[0.33, 0.33, 0.34], 0x00000000, 0x00000000, 2], - [[0.33, 0.33, 0.34], 0x00000000, 0x00000001, 0], - [[0.33, 0.33, 0.34], 0x8015406f, 0x7ef49b98, 2], - [[0.33, 0.33, 0.34], 0x3b2e7d90, 0xca87df4d, 1], - [[0.33, 0.33, 0.34], 0x52c1f657, 0xd248bb2e, 0], - [[0.33, 0.33, 0.34], 0x865a84d0, 0xaa22d41a, 0], - [[0.33, 0.33, 0.34], 0x27d1dc86, 0x845461b9, 1], - ], - }; - - for (const [unit, tests] of Object.entries(testCases)) { - const assigner = new VariantAssigner(hashUnit(unit)); - for (const testCase of tests) { - const variant = assigner.assign(testCase[0], testCase[1], testCase[2]); - expect(variant).toBe(testCase[3]); - } - } - done(); - }); -}); diff --git a/src/__tests__/client.test.js b/src/__tests__/client.test.js deleted file mode 100644 index 7834ff4..0000000 --- a/src/__tests__/client.test.js +++ /dev/null @@ -1,1071 +0,0 @@ -import Client from "../client"; -// eslint-disable-next-line no-shadow -import fetch from "../fetch"; -// eslint-disable-next-line no-shadow -import { AbortController } from "../abort"; -import { AbortError, RetryError, TimeoutError } from "../errors"; //eslint-disable-line no-shadow -import { SDK_VERSION } from "../version"; - -jest.mock("../fetch"); - -describe("Client", () => { - beforeEach(() => { - jest.useFakeTimers("legacy"); - jest.spyOn(global, "setTimeout"); - }); - - afterEach(() => { - fetch.mockReset(); - }); - - function advanceFakeTimers() { - return new Promise((resolve) => { - let iterations = 0; - - const advance = () => { - jest.advanceTimersByTime(100); - - if (++iterations <= 50) { - Promise.resolve().then(advance); - } else { - resolve(); - } - }; - - Promise.resolve().then(advance); - }); - } - - const endpoint = "test.absmartly.com:8080/v1"; - const apiKey = "5ebf06d8cb5d8137290c4abb64155584fbdb64d8"; - const agent = "javascript-client"; - const environment = "test"; - const application = { - name: "test_app", - version: 1_000_000, - }; - - const units = { - session_id: "dca367dcda209b5197f5f83aee862c7bfb09dc68", - }; - - const clientOptions = { - endpoint, - agent, - environment, - apiKey, - application, - keepalive: true, - timeout: 5000, - retries: 3, - }; - - const defaultMockResponse = { - units, - }; - - const goals = [ - { - name: "goal1", - value: [123], - achievedAt: 123456789, - }, - ]; - - const exposures = [ - { - name: "exp_test", - variant: 1, - exposedAt: 123456789, - assigned: true, - }, - ]; - - const attributes = [ - { - name: "exp_test", - value: "1", - setAt: 123456789, - }, - ]; - - const publishedAt = 1234567890; - - function responseMock(statusCode, statusText, response) { - return { - ok: statusCode >= 200 && statusCode <= 299, - status: statusCode, - statusText, - text: () => Promise.resolve(response), - json: () => Promise.resolve(JSON.parse(JSON.stringify(response))), - }; - } - - function mockFetch(delay, response) { - return (url, opts) => { - if (delay > 0) { - return new Promise((resolve, reject) => { - const timeout = setTimeout(() => { - resolve(response); - }, delay); - - if (opts.signal) { - opts.signal.onabort = () => { - clearTimeout(timeout); - reject(new AbortError()); - }; - } - }); - } - - return new Promise.resolve(response); - }; - } - - it("constructor() should validate options", (done) => { - const deleteOption = (options, key) => { - const result = Object.assign({}, options); - delete result[key]; - - return result; - }; - - const emptyOption = (options, key) => { - const result = Object.assign({}, options); - result[key] = ""; - - return result; - }; - - for (const key of ["apiKey", "application", "endpoint", "environment"]) { - expect(() => new Client(deleteOption(clientOptions, key))).toThrow(); - expect(() => new Client(emptyOption(clientOptions, key))).toThrow(); - } - expect(() => new Client(emptyOption(clientOptions, "agent"))).toThrow(); - - done(); - }); - - it("constructor() should accept string application", (done) => { - const options = Object.assign({}, clientOptions, { application: "website" }); - - expect(() => new Client(options)).not.toThrow(); - - done(); - }); - - it("createContext() calls endpoint", (done) => { - fetch.mockResolvedValue(responseMock(200, "OK", defaultMockResponse)); - - const client = new Client(clientOptions); - - client - .createContext({ - units, - }) - .then((response) => { - expect(fetch).toHaveBeenCalledTimes(1); - expect(fetch).toHaveBeenCalledWith(`${endpoint}/context`, { - method: "POST", - headers: { - "Content-Type": "application/json", - "X-API-Key": apiKey, - "X-Agent": "javascript-client", - "X-Environment": "test", - "X-Application": "test_app", - "X-Application-Version": 1000000, - }, - body: JSON.stringify({ - units, - }), - keepalive: true, - signal: expect.any(Object), - }); - - expect(response).toStrictEqual(defaultMockResponse); - - done(); - }); - }); - - it("getContext() calls endpoint with correct query", (done) => { - fetch.mockResolvedValueOnce(responseMock(200, "OK", defaultMockResponse)); - - const client = new Client(clientOptions); - - client.getContext().then((response) => { - expect(fetch).toHaveBeenCalledTimes(1); - expect(fetch).toHaveBeenCalledWith(`${endpoint}/context?application=test_app&environment=test`, { - method: "GET", - keepalive: true, - signal: expect.any(Object), - }); - - expect(response).toEqual(defaultMockResponse); - - done(); - }); - }); - - it("request() retries on connection error", (done) => { - fetch - .mockRejectedValueOnce(new Error("error 1")) - .mockRejectedValueOnce(new Error("error 2")) - .mockResolvedValueOnce(responseMock(200, "OK", defaultMockResponse)); - - const client = new Client(clientOptions); - - client - .request({ - auth: true, - method: "PUT", - path: "/context", - query: { a: 1 }, - body: {}, - }) - .then((response) => { - expect(fetch).toHaveBeenCalledTimes(3); - expect(fetch).toHaveBeenLastCalledWith(`${endpoint}/context?a=1`, { - method: "PUT", - headers: { - "Content-Type": "application/json", - "X-API-Key": apiKey, - "X-Agent": "javascript-client", - "X-Environment": "test", - "X-Application": "test_app", - "X-Application-Version": 1000000, - }, - body: JSON.stringify({}), - keepalive: true, - signal: expect.any(Object), - }); - - expect(response).toEqual(defaultMockResponse); - - done(); - }); - - advanceFakeTimers(); - }); - - it("request() stops retrying after options.retries", (done) => { - fetch - .mockRejectedValueOnce(new Error("error 1")) - .mockRejectedValueOnce(new Error("error 2")) - .mockRejectedValueOnce(new Error("error 3")) - .mockRejectedValueOnce(new Error("error 4")) - .mockRejectedValueOnce(new Error("error 5")) - .mockRejectedValueOnce(new Error("error 6")) - .mockRejectedValueOnce(new Error("error 7")); - - jest.spyOn(Math, "random"); - Math.random.mockReturnValue(0.0); - - const options = Object.assign({}, clientOptions, { retries: 5, timeout: 5000 }); - const client = new Client(options); - - client - .request({ - auth: true, - method: "PUT", - path: "/context", - query: { a: 1 }, - body: {}, - }) - .catch((error) => { - expect(fetch).toHaveBeenCalledTimes(6); - expect(fetch).toHaveBeenLastCalledWith(`${endpoint}/context?a=1`, { - method: "PUT", - headers: { - "Content-Type": "application/json", - "X-API-Key": apiKey, - "X-Agent": "javascript-client", - "X-Environment": "test", - "X-Application": "test_app", - "X-Application-Version": 1000000, - }, - body: JSON.stringify({}), - keepalive: true, - signal: expect.any(Object), - }); - - expect(error).toBeInstanceOf(RetryError); - expect(setTimeout).toHaveBeenCalledTimes(6); - expect(setTimeout.mock.calls.map((x) => x[1]).reduce((x, y) => x + y)).toBeLessThanOrEqual(5000 + 1675); - - done(); - }); - - advanceFakeTimers(); - }); - - it("request() does not retry with options.retries == 0", (done) => { - fetch.mockRejectedValueOnce(new Error("error 1")); - - jest.spyOn(Math, "random"); - Math.random.mockReturnValue(0.0); - - const options = Object.assign({}, clientOptions, { retries: 0, timeout: 5000 }); - const client = new Client(options); - - client - .request({ - auth: true, - method: "PUT", - path: "/context", - query: { a: 1 }, - body: {}, - }) - .catch((error) => { - expect(fetch).toHaveBeenCalledTimes(1); - expect(fetch).toHaveBeenLastCalledWith(`${endpoint}/context?a=1`, { - method: "PUT", - headers: { - "Content-Type": "application/json", - "X-API-Key": apiKey, - "X-Agent": "javascript-client", - "X-Environment": "test", - "X-Application": "test_app", - "X-Application-Version": 1000000, - }, - body: JSON.stringify({}), - keepalive: true, - signal: expect.any(Object), - }); - - expect(error.message).toEqual("error 1"); - expect(setTimeout).toHaveBeenCalledTimes(1); - expect(setTimeout.mock.calls.map((x) => x[1]).reduce((x, y) => x + y)).toBe(5000); - - done(); - }); - - advanceFakeTimers(); - }); - - it("request() stops retrying after options.timeout", (done) => { - fetch - .mockRejectedValueOnce(new Error("error 1")) - .mockRejectedValueOnce(new Error("error 2")) - .mockRejectedValueOnce(new Error("error 3")) - .mockRejectedValueOnce(new Error("error 4")) - .mockRejectedValueOnce(new Error("error 5")) - .mockRejectedValueOnce(new Error("error 6")) - .mockRejectedValueOnce(new Error("error 7")); - - jest.spyOn(Math, "random"); - Math.random.mockReturnValue(1.0); - - const options = Object.assign({}, clientOptions, { retries: 5, timeout: 5000 }); - const client = new Client(options); - - client - .request({ - auth: true, - method: "PUT", - path: "/context", - query: { a: 1 }, - body: {}, - }) - .catch((error) => { - expect(fetch).toHaveBeenCalledTimes(6); - expect(fetch).toHaveBeenLastCalledWith(`${endpoint}/context?a=1`, { - method: "PUT", - headers: { - "Content-Type": "application/json", - "X-API-Key": apiKey, - "X-Agent": "javascript-client", - "X-Environment": "test", - "X-Application": "test_app", - "X-Application-Version": 1000000, - }, - body: JSON.stringify({}), - keepalive: true, - signal: expect.any(Object), - }); - - expect(error).toBeInstanceOf(RetryError); - expect(setTimeout).toHaveBeenCalledTimes(6); - expect(setTimeout.mock.calls.map((x) => x[1]).reduce((x, y) => x + y)).toBeCloseTo(5000 + 1675, 3); - - done(); - }); - - advanceFakeTimers(); - }); - - it("request() does not abort before options.timeout", (done) => { - fetch.mockImplementation(mockFetch(1000, responseMock(200, "OK", defaultMockResponse))); - - const options = Object.assign({}, clientOptions, { timeout: 2000 }); - const client = new Client(options); - - client - .request({ - auth: true, - method: "PUT", - path: "/context", - query: { a: 1 }, - body: {}, - }) - .then((response) => { - expect(response).toStrictEqual(defaultMockResponse); - - done(); - }) - .catch((error) => { - done(error); - }); - - advanceFakeTimers(); - }); - - it("request() aborts after options.timeout", (done) => { - fetch.mockImplementation(mockFetch(2000, responseMock(200, "OK", defaultMockResponse))); - - const options = Object.assign({}, clientOptions, { timeout: 1000 }); - const client = new Client(options); - - client - .request({ - auth: true, - method: "PUT", - path: "/context", - query: { a: 1 }, - body: {}, - }) - .then(() => { - done("unexpected"); - }) - .catch((error) => { - expect(error).toBeInstanceOf(TimeoutError); - - done(); - }); - - advanceFakeTimers(); - }); - - it("request() aborts when abort() is called", (done) => { - fetch.mockImplementation(mockFetch(3000, responseMock(200, "OK", defaultMockResponse))); - - const aborter = new AbortController(); - const options = Object.assign({}, clientOptions, { timeout: 5000 }); - const client = new Client(options); - - client - .request({ - auth: true, - method: "PUT", - path: "/context", - query: { a: 1 }, - body: {}, - signal: aborter.signal, - }) - .then(() => { - done("unexpected"); - }) - .catch((error) => { - expect(error).toBeInstanceOf(AbortError); - - done(); - }); - - setTimeout(() => { - aborter.abort(); - }, 500); - - advanceFakeTimers(); - }); - - it("request() aborts when abort() is called during a retry wait", (done) => { - fetch - .mockRejectedValueOnce(new Error("error 1")) - .mockRejectedValueOnce(new Error("error 2")) - .mockRejectedValueOnce(new Error("error 3")) - .mockRejectedValueOnce(new Error("error 4")) - .mockRejectedValueOnce(new Error("error 5")) - .mockRejectedValueOnce(new Error("error 6")) - .mockRejectedValueOnce(new Error("error 7")); - - jest.spyOn(Math, "random"); - Math.random.mockReturnValue(1.0); - - const aborter = new AbortController(); - const options = Object.assign({}, clientOptions, { retries: 7, timeout: 5000 }); - const client = new Client(options); - - client - .request({ - auth: true, - method: "PUT", - path: "/context", - query: { a: 1 }, - body: {}, - signal: aborter.signal, - }) - .then(() => { - done("unexpected"); - }) - .catch((error) => { - expect(error).toBeInstanceOf(AbortError); - - done(); - }); - - setTimeout(() => { - aborter.abort(); - }, 500); - - advanceFakeTimers(); - }); - - it("request() cleans up signal event listener on error", (done) => { - fetch.mockRejectedValueOnce(new Error("error 1")); - - const aborter = new AbortController(); - jest.spyOn(aborter.signal, "addEventListener"); - jest.spyOn(aborter.signal, "removeEventListener"); - - const options = Object.assign({}, clientOptions, { retries: 0, timeout: 5000 }); - const client = new Client(options); - - client - .request({ - auth: true, - method: "PUT", - path: "/context", - query: { a: 1 }, - body: {}, - signal: aborter.signal, - }) - .then(() => { - done("unexpected"); - }) - .catch(() => { - expect(aborter.signal.addEventListener).toHaveBeenCalledTimes(1); - expect(aborter.signal.removeEventListener).toHaveBeenCalledTimes(1); - - done(); - }); - }); - - it("request() cleans up signal event listener on success", (done) => { - fetch.mockResolvedValueOnce(responseMock(200, "OK", defaultMockResponse)); - - const aborter = new AbortController(); - jest.spyOn(aborter.signal, "addEventListener"); - jest.spyOn(aborter.signal, "removeEventListener"); - - const options = Object.assign({}, clientOptions, { retries: 0, timeout: 5000 }); - const client = new Client(options); - - client - .request({ - auth: true, - method: "PUT", - path: "/context", - query: { a: 1 }, - body: {}, - signal: aborter.signal, - }) - .then(() => { - expect(aborter.signal.addEventListener).toHaveBeenCalledTimes(1); - expect(aborter.signal.removeEventListener).toHaveBeenCalledTimes(1); - - done(); - }) - .catch((error) => { - done(error); - }); - }); - - it("request() stops retrying on bad request", (done) => { - fetch.mockResolvedValueOnce(responseMock(400, "bad request", "bad request error text")); - - const client = new Client(clientOptions); - - client - .request({ - auth: true, - method: "POST", - path: "/context", - query: { a: 1 }, - body: {}, - }) - .catch((error) => { - expect(fetch).toHaveBeenCalledTimes(1); - expect(fetch).toHaveBeenLastCalledWith(`${endpoint}/context?a=1`, { - method: "POST", - headers: { - "Content-Type": "application/json", - "X-API-Key": apiKey, - "X-Agent": "javascript-client", - "X-Environment": "test", - "X-Application": "test_app", - "X-Application-Version": 1000000, - }, - body: JSON.stringify({}), - keepalive: true, - signal: expect.any(Object), - }); - - expect(error.message).toEqual("bad request error text"); - - done(); - }); - }); - - it("request() retries on server errors", (done) => { - fetch - .mockResolvedValueOnce(responseMock(500, "server error", "server error text")) - .mockResolvedValueOnce(responseMock(200, "OK", defaultMockResponse)); - - const client = new Client(clientOptions); - - client - .request({ - auth: true, - method: "POST", - path: "/context", - query: { a: 1 }, - body: {}, - }) - .then((response) => { - expect(fetch).toHaveBeenCalledTimes(2); - expect(fetch).toHaveBeenLastCalledWith(`${endpoint}/context?a=1`, { - method: "POST", - headers: { - "Content-Type": "application/json", - "X-API-Key": apiKey, - "X-Agent": "javascript-client", - "X-Environment": "test", - "X-Application": "test_app", - "X-Application-Version": 1000000, - }, - body: JSON.stringify({}), - keepalive: true, - signal: expect.any(Object), - }); - - expect(response).toEqual(defaultMockResponse); - - done(); - }); - - advanceFakeTimers(); - }); - - it("request() should encode url query parameters", (done) => { - fetch.mockResolvedValueOnce(responseMock(200, "OK", defaultMockResponse)); - - const client = new Client(clientOptions); - - client - .request({ - auth: true, - method: "PUT", - path: "/context", - query: { a: 1, b: "ã=á" }, - body: {}, - }) - .then((response) => { - expect(fetch).toHaveBeenCalledTimes(1); - expect(fetch).toHaveBeenLastCalledWith(`${endpoint}/context?a=1&b=%C3%A3%3D%C3%A1`, { - method: "PUT", - headers: { - "Content-Type": "application/json", - "X-API-Key": apiKey, - "X-Agent": "javascript-client", - "X-Environment": "test", - "X-Application": "test_app", - "X-Application-Version": 1000000, - }, - body: JSON.stringify({}), - keepalive: true, - signal: expect.any(Object), - }); - - expect(response).toEqual(defaultMockResponse); - - done(); - }); - }); - - it("request() should omit query parameters if dict empty", (done) => { - fetch.mockResolvedValueOnce(responseMock(200, "OK", defaultMockResponse)); - - const client = new Client(clientOptions); - - client - .request({ - auth: true, - method: "PUT", - path: "/context", - query: {}, - body: {}, - }) - .then((response) => { - expect(fetch).toHaveBeenCalledTimes(1); - expect(fetch).toHaveBeenLastCalledWith(`${endpoint}/context`, { - method: "PUT", - headers: { - "Content-Type": "application/json", - "X-API-Key": apiKey, - "X-Agent": "javascript-client", - "X-Environment": "test", - "X-Application": "test_app", - "X-Application-Version": 1000000, - }, - body: JSON.stringify({}), - keepalive: true, - signal: expect.any(Object), - }); - - expect(response).toEqual(defaultMockResponse); - - done(); - }); - }); - - it("request() should call fetch with an empty body if not specified", (done) => { - fetch.mockResolvedValueOnce(responseMock(200, "OK", defaultMockResponse)); - - const client = new Client(clientOptions); - - client - .request({ - auth: true, - method: "PUT", - path: "/context", - }) - .then((response) => { - expect(fetch).toHaveBeenCalledTimes(1); - expect(fetch).toHaveBeenLastCalledWith(`${endpoint}/context`, { - method: "PUT", - headers: { - "Content-Type": "application/json", - "X-API-Key": apiKey, - "X-Agent": "javascript-client", - "X-Environment": "test", - "X-Application": "test_app", - "X-Application-Version": 1000000, - }, - body: undefined, - keepalive: true, - signal: expect.any(Object), - }); - - expect(response).toEqual(defaultMockResponse); - - done(); - }); - }); - - it("request() should set applications headers for string application", (done) => { - fetch.mockResolvedValueOnce(responseMock(200, "OK", defaultMockResponse)); - - const client = new Client(Object.assign({}, clientOptions, { application: "website" })); - - client - .request({ - auth: true, - method: "PUT", - path: "/context", - }) - .then((response) => { - expect(fetch).toHaveBeenCalledTimes(1); - expect(fetch).toHaveBeenLastCalledWith(`${endpoint}/context`, { - method: "PUT", - headers: { - "Content-Type": "application/json", - "X-API-Key": apiKey, - "X-Agent": "javascript-client", - "X-Environment": "test", - "X-Application": "website", - "X-Application-Version": 0, - }, - keepalive: true, - body: undefined, - signal: expect.any(Object), - }); - - expect(response).toEqual(defaultMockResponse); - - done(); - }); - }); - - it("request() should not send headers when auth argument is false", (done) => { - fetch.mockResolvedValueOnce(responseMock(200, "OK", defaultMockResponse)); - - const client = new Client(Object.assign({}, clientOptions, { application: "website" })); - - client - .request({ - auth: false, - method: "PUT", - path: "/context", - }) - .then((response) => { - expect(fetch).toHaveBeenCalledTimes(1); - expect(fetch).toHaveBeenLastCalledWith(`${endpoint}/context`, { - method: "PUT", - body: undefined, - keepalive: true, - signal: expect.any(Object), - }); - - expect(response).toEqual(defaultMockResponse); - - done(); - }); - }); - - it("publish() calls endpoint", (done) => { - fetch.mockResolvedValueOnce(responseMock(200, "OK", defaultMockResponse)); - - const client = new Client(clientOptions); - - client - .publish({ - units, - publishedAt, - sdkVersion: SDK_VERSION, - goals, - exposures, - attributes, - }) - .then((response) => { - expect(fetch).toHaveBeenCalledTimes(1); - expect(fetch).toHaveBeenCalledWith(`${endpoint}/context`, { - method: "PUT", - headers: { - "Content-Type": "application/json", - "X-API-Key": apiKey, - "X-Agent": "javascript-client", - "X-Environment": "test", - "X-Application": "test_app", - "X-Application-Version": 1000000, - }, - keepalive: true, - body: JSON.stringify({ - units, - publishedAt, - sdkVersion: SDK_VERSION, - goals, - exposures, - attributes, - }), - signal: expect.any(Object), - }); - - expect(response).toEqual(defaultMockResponse); - - done(); - }); - }); - - it("publish() should omit empty arrays", (done) => { - fetch.mockResolvedValueOnce(responseMock(200, "OK", defaultMockResponse)); - - const client = new Client(clientOptions); - - client - .publish({ - units, - publishedAt, - sdkVersion: SDK_VERSION, - goals: [], - exposures: [], - }) - .then((response) => { - expect(fetch).toHaveBeenCalledTimes(1); - expect(fetch).toHaveBeenCalledWith(`${endpoint}/context`, { - method: "PUT", - headers: { - "Content-Type": "application/json", - "X-API-Key": apiKey, - "X-Agent": "javascript-client", - "X-Environment": "test", - "X-Application": "test_app", - "X-Application-Version": 1000000, - }, - keepalive: true, - body: JSON.stringify({ - units, - publishedAt, - sdkVersion: SDK_VERSION, - }), - signal: expect.any(Object), - }); - - expect(response).toEqual(defaultMockResponse); - - done(); - }); - }); - - it("publish() should set publishedAt if not present", (done) => { - fetch.mockResolvedValueOnce(responseMock(200, "OK", defaultMockResponse)); - jest.spyOn(Date, "now").mockReturnValue(publishedAt + 100); - - const client = new Client(clientOptions); - - client - .publish({ - units, - sdkVersion: SDK_VERSION, - goals: [], - exposures: [], - }) - .then((response) => { - expect(fetch).toHaveBeenCalledTimes(1); - expect(fetch).toHaveBeenCalledWith(`${endpoint}/context`, { - method: "PUT", - headers: { - "Content-Type": "application/json", - "X-API-Key": apiKey, - "X-Agent": "javascript-client", - "X-Environment": "test", - "X-Application": "test_app", - "X-Application-Version": 1000000, - }, - keepalive: true, - body: JSON.stringify({ - units, - publishedAt: publishedAt + 100, - sdkVersion: SDK_VERSION, - }), - signal: expect.any(Object), - }); - - expect(response).toEqual(defaultMockResponse); - - done(); - }); - }); - - it("constructor() should accept custom agent string", (done) => { - fetch.mockResolvedValueOnce(responseMock(200, "OK", defaultMockResponse)); - - const customAgent = "custom-agent"; - const client = new Client({ ...clientOptions, agent: customAgent }); - - client - .request({ - auth: true, - method: "PUT", - path: "/context", - }) - .then(() => { - expect(fetch).toHaveBeenCalledTimes(1); - expect(fetch).toHaveBeenCalledWith(`${endpoint}/context`, { - method: "PUT", - headers: { - "Content-Type": "application/json", - "X-API-Key": apiKey, - "X-Agent": customAgent, - "X-Environment": "test", - "X-Application": "test_app", - "X-Application-Version": 1000000, - }, - body: undefined, - keepalive: true, - signal: expect.any(Object), - }); - - done(); - }); - }); - - it("publish() should include sdkVersion in body", (done) => { - fetch.mockResolvedValueOnce(responseMock(200, "OK", defaultMockResponse)); - - const client = new Client(clientOptions); - - client - .publish({ - units, - publishedAt, - sdkVersion: "1.2.3", - goals: [], - exposures: [], - }) - .then(() => { - const body = JSON.parse(fetch.mock.calls[0][1].body); - expect(body.sdkVersion).toEqual("1.2.3"); - - done(); - }); - }); - - it("getAgent() should return default agent when not specified", () => { - const { agent: _, ...optionsWithoutAgent } = clientOptions; - const client = new Client(optionsWithoutAgent); - expect(client.getAgent()).toEqual("javascript-client"); - }); - - it("getAgent() should return custom agent when specified", () => { - const client = new Client({ ...clientOptions, agent: "custom-sdk" }); - expect(client.getAgent()).toEqual("custom-sdk"); - }); - - it("getApplication() should return normalized application object", () => { - const client = new Client(clientOptions); - expect(client.getApplication()).toEqual({ name: "test_app", version: 1000000 }); - }); - - it("getApplication() should normalize string application to object", () => { - const client = new Client({ ...clientOptions, application: "website" }); - expect(client.getApplication()).toEqual({ name: "website", version: 0 }); - }); - - it("getApplication() should accept semver string version", () => { - const client = new Client({ ...clientOptions, application: { name: "website", version: "1.2.3" } }); - expect(client.getApplication()).toEqual({ name: "website", version: "1.2.3" }); - }); - - it("getEnvironment() should return the environment", () => { - const client = new Client(clientOptions); - expect(client.getEnvironment()).toEqual(environment); - }); - - it("publish() should not have the keepalive flag if specified", (done) => { - fetch.mockResolvedValueOnce(responseMock(200, "OK", defaultMockResponse)); - - const client = new Client({ ...clientOptions, keepalive: false }); - - client - .publish({ - units, - publishedAt, - goals: [], - exposures: [], - }) - .then((response) => { - expect(fetch).toHaveBeenCalledTimes(1); - expect(fetch).toHaveBeenCalledWith(`${endpoint}/context`, { - method: "PUT", - headers: { - "Content-Type": "application/json", - "X-API-Key": apiKey, - "X-Agent": "javascript-client", - "X-Environment": "test", - "X-Application": "test_app", - "X-Application-Version": 1000000, - }, - keepalive: false, - body: JSON.stringify({ - units, - publishedAt, - }), - signal: expect.any(Object), - }); - - expect(response).toEqual(defaultMockResponse); - - done(); - }); - }); -}); diff --git a/src/__tests__/config.test.js b/src/__tests__/config.test.js deleted file mode 100644 index 5be597c..0000000 --- a/src/__tests__/config.test.js +++ /dev/null @@ -1,236 +0,0 @@ -import { mergeConfig } from "../config"; -import Context from "../context"; - -jest.mock("../context"); - -describe("Config", () => { - describe("mergeConfig()", () => { - it("should create getters that call context.variable()", (done) => { - const context = new Context(); - - const variableKeys = { - button: "exp_test_abc", - "banner.border": "exp_test_ab", - "banner.size": "exp_test_ab", - "home.arrow.direction": "exp_test_arrow", - }; - - const expectedValues = { - button: true, - "banner.border": 10, - "banner.size": 812, - "home.arrow.direction": "up", - }; - - context.variableKeys.mockReturnValue(variableKeys); - context.variableValue.mockImplementation((key) => expectedValues[key]); - - const previousConfig = { - button: false, - banner: { - size: 420, - border: 0, - }, - home: { - arrow: { - direction: "down", - }, - }, - other: "unused", - }; - - const expectedConfig = { - button: true, - banner: { - size: 812, - border: 10, - }, - home: { - arrow: { - direction: "up", - }, - }, - other: "unused", - }; - - const actual = mergeConfig(context, previousConfig); - expect(actual).not.toBe(previousConfig); // should be a clone and new properties are not values, but have accessors - expect(actual).toMatchObject(expectedConfig); - expect(context.variableValue).toHaveBeenCalledTimes(4); // called during equality check above - context.variableValue.mockClear(); - - expect(actual.button).toEqual(expectedConfig.button); - expect(context.variableValue).toHaveBeenCalledTimes(1); - expect(context.variableValue).toHaveBeenCalledWith("button", previousConfig.button); - context.variableValue.mockClear(); - - expect(actual.banner.border).toEqual(expectedConfig.banner.border); - expect(context.variableValue).toHaveBeenCalledTimes(1); - expect(context.variableValue).toHaveBeenCalledWith("banner.border", previousConfig.banner.border); - context.variableValue.mockClear(); - - expect(actual.banner.size).toEqual(expectedConfig.banner.size); - expect(context.variableValue).toHaveBeenCalledTimes(1); - expect(context.variableValue).toHaveBeenCalledWith("banner.size", previousConfig.banner.size); - context.variableValue.mockClear(); - - expect(actual.home.arrow.direction).toEqual(expectedConfig.home.arrow.direction); - expect(context.variableValue).toHaveBeenCalledTimes(1); - expect(context.variableValue).toHaveBeenCalledWith( - "home.arrow.direction", - previousConfig.home.arrow.direction - ); - context.variableValue.mockClear(); - - expect(actual.other).toEqual(expectedConfig.other); - expect(context.variableValue).not.toHaveBeenCalled(); - - done(); - }); - - it("should warn about mismatching object merges", (done) => { - jest.spyOn(console, "warn").mockImplementation(() => {}); - - const context = new Context(); - - const variableKeys = { - "button.active": "exp_test_abc", - "banner.border": "exp_test_ab", - "banner.size": "exp_test_ab", - "home.arrow.direction": "exp_test_arrow", - }; - - const expectedValues = { - "button.active": true, - "banner.border": 10, - "banner.size": 812, - "home.arrow.direction": "up", - }; - - context.variableKeys.mockReturnValue(variableKeys); - context.variableValue.mockImplementation((key) => expectedValues[key]); - - const previousConfig = { - button: true, - banner: { - size: 420, - border: 0, - }, - home: { - arrow: "down", - }, - other: "unused", - }; - - const expectedConfig = { - button: { - active: true, - }, - banner: { - size: 812, - border: 10, - }, - home: { - arrow: { - direction: "up", - }, - }, - other: "unused", - }; - - const actual = mergeConfig(context, previousConfig); - expect(actual).not.toBe(previousConfig); // should be a clone and new properties are not values, but have accessors - expect(actual).toMatchObject(expectedConfig); - expect(context.variableValue).toHaveBeenCalledTimes(4); // called during equality check above - context.variableValue.mockClear(); - - expect(console.warn).toHaveBeenCalledTimes(2); - expect(console.warn).toHaveBeenCalledWith( - "Config key 'button.active' for experiment 'exp_test_abc' is overriding non-object value at 'button' with an object." - ); - expect(console.warn).toHaveBeenCalledWith( - "Config key 'home.arrow.direction' for experiment 'exp_test_arrow' is overriding non-object value at 'home.arrow' with an object." - ); - - expect(actual.button.active).toEqual(expectedConfig.button.active); - expect(context.variableValue).toHaveBeenCalledTimes(1); - expect(context.variableValue).toHaveBeenCalledWith("button.active", undefined); - context.variableValue.mockClear(); - - expect(actual.home.arrow.direction).toEqual(expectedConfig.home.arrow.direction); - expect(context.variableValue).toHaveBeenCalledTimes(1); - expect(context.variableValue).toHaveBeenCalledWith("home.arrow.direction", undefined); - context.variableValue.mockClear(); - - done(); - }); - - it("should error with multiple experiments overriding key", (done) => { - jest.spyOn(console, "error").mockImplementation(() => {}); - - jest.spyOn(console, "warn").mockImplementation(() => {}); - - const context = new Context(); - - const variableKeys = { - "button.active": true, - "banner.border": "exp_test_ab", - "banner.size": "exp_test_abc", - "home.arrow": "exp_test_arrow", - "home.arrow.direction": "exp_test_arrow_direction", - }; - - const expectedValues = { - "button.active": true, - "banner.border": 10, - "banner.size": 812, - "home.arrow": "up", - "home.arrow.direction": "up", - }; - - context.variableKeys.mockReturnValue(variableKeys); - context.variableValue.mockImplementation((key) => expectedValues[key]); - - const previousConfig = { - button: { - active: false, - }, - banner: { - size: 420, - border: 0, - }, - home: { - arrow: "down", - }, - other: "unused", - }; - - const expectedConfig = { - button: { - active: true, - }, - banner: { - size: 812, - border: 10, - }, - home: { - arrow: "up", - }, - other: "unused", - }; - - const actual = mergeConfig(context, previousConfig); - expect(actual).not.toBe(previousConfig); // should be a clone and new properties are not values, but have accessors - expect(actual).toMatchObject(expectedConfig); - expect(context.variableValue).toHaveBeenCalledTimes(4); // called during equality check above - context.variableValue.mockClear(); - - expect(console.error).toHaveBeenCalledTimes(1); - expect(console.error).toHaveBeenCalledWith( - "Config key 'home.arrow' already set by experiment 'exp_test_arrow'." - ); - - done(); - }); - }); -}); diff --git a/src/__tests__/context.test.js b/src/__tests__/context.test.js deleted file mode 100644 index bac768c..0000000 --- a/src/__tests__/context.test.js +++ /dev/null @@ -1,4178 +0,0 @@ -import Client from "../client"; -import SDK from "../sdk"; -import Context from "../context"; -import { hashUnit } from "../utils"; -import clone from "rfdc/default"; -import { ContextPublisher } from "../publisher"; -import { ContextDataProvider } from "../provider"; -import { SDK_VERSION } from "../version"; - -jest.mock("../client"); -jest.mock("../sdk"); -jest.mock("../provider"); -jest.mock("../publisher"); - -describe("Context", () => { - const contextParams = { - units: { - session_id: "e791e240fcd3df7d238cfc285f475e8152fcc0ec", - user_id: 12317303, - }, - }; - - const publishUnits = Object.entries(contextParams.units).map((x) => ({ type: x[0], uid: hashUnit(x[1]) })); - - const units = { - session_id: "e791e240fcd3df7d238cfc285f475e8152fcc0ec", - user_id: "123456789", - email: "bleh@absmartly.com", - }; - - const getContextResponse = { - experiments: [ - { - id: 1, - name: "exp_test_ab", - iteration: 1, - unitType: "session_id", - seedHi: 3603515, - seedLo: 233373850, - split: [0.5, 0.5], - trafficSeedHi: 449867249, - trafficSeedLo: 455443629, - trafficSplit: [0.0, 1.0], - fullOnVariant: 0, - applications: [ - { - name: "website", - }, - ], - variants: [ - { - name: "A", - config: null, - }, - { - name: "B", - config: '{"banner.border":1,"banner.size":"large"}', - }, - ], - audience: null, - customFieldValues: null, - }, - { - id: 2, - name: "exp_test_abc", - iteration: 1, - unitType: "session_id", - seedHi: 55006150, - seedLo: 47189152, - split: [0.34, 0.33, 0.33], - trafficSeedHi: 705671872, - trafficSeedLo: 212903484, - trafficSplit: [0.0, 1.0], - fullOnVariant: 0, - applications: [ - { - name: "website", - }, - ], - variants: [ - { - name: "A", - config: null, - }, - { - name: "B", - config: '{"button.color":"blue"}', - }, - { - name: "C", - config: '{"button.color":"red"}', - }, - ], - audience: "", - customFieldValues: [ - { - name: "country", - value: "US,PT,ES,DE,FR", - type: "string", - }, - { - name: "json_object", - value: '{"123":1,"456":0}', - type: "json", - }, - { - name: "json_array", - value: '["hello", "world"]', - type: "json", - }, - { - name: "json_number", - value: "123", - type: "json", - }, - { - name: "json_string", - value: '"hello"', - type: "json", - }, - { - name: "json_boolean", - value: "true", - type: "json", - }, - { - name: "json_null", - value: "null", - type: "json", - }, - { - name: "json_invalid", - value: "invalid", - type: "json", - }, - ], - }, - { - id: 3, - name: "exp_test_not_eligible", - iteration: 1, - unitType: "user_id", - seedHi: 503266407, - seedLo: 144942754, - split: [0.34, 0.33, 0.33], - trafficSeedHi: 87768905, - trafficSeedLo: 511357582, - trafficSplit: [0.99, 0.01], - fullOnVariant: 0, - applications: [ - { - name: "website", - }, - ], - variants: [ - { - name: "A", - config: null, - }, - { - name: "B", - config: '{"card.width":"80%"}', - }, - { - name: "C", - config: '{"card.width":"75%"}', - }, - ], - audience: "{}", - customFieldValues: null, - }, - { - id: 4, - name: "exp_test_fullon", - iteration: 1, - unitType: "session_id", - seedHi: 856061641, - seedLo: 990838475, - split: [0.25, 0.25, 0.25, 0.25], - trafficSeedHi: 360868579, - trafficSeedLo: 330937933, - trafficSplit: [0.0, 1.0], - fullOnVariant: 2, - applications: [ - { - name: "website", - }, - ], - variants: [ - { - name: "A", - config: null, - }, - { - name: "B", - config: '{"submit.color":"red","submit.shape":"circle"}', - }, - { - name: "C", - config: '{"submit.color":"blue","submit.shape":"rect"}', - }, - { - name: "D", - config: '{"submit.color":"green","submit.shape":"square"}', - }, - ], - audience: "null", - customFieldValues: null, - }, - { - id: 5, - name: "exp_test_custom_fields", - iteration: 1, - unitType: "session_id", - seedHi: 9372617, - seedLo: 121364805, - split: [0.5, 0.5], - trafficSeedHi: 318746944, - trafficSeedLo: 359812364, - trafficSplit: [0.0, 1.0], - fullOnVariant: 0, - applications: [ - { - name: "website", - }, - ], - variants: [ - { - name: "A", - config: null, - }, - { - name: "B", - config: '{"submit.size":"sm"}', - }, - ], - audience: null, - customFieldValues: [ - { - name: "country", - value: "US,PT,ES", - type: "string", - }, - { - name: "languages", - value: "en-US,en-GB,pt-PT,pt-BR,es-ES,es-MX", - type: "string", - }, - { - name: "text_field", - value: "hello text", - type: "text", - }, - { - name: "string_field", - value: "hello string", - type: "string", - }, - { - name: "number_field", - value: "123", - type: "number", - }, - { - name: "boolean_field", - value: "true", - type: "boolean", - }, - { - name: "false_boolean_field", - value: "false", - type: "boolean", - }, - { - name: "invalid_type_field", - value: "invalid", - type: "invalid", - }, - ], - }, - ], - }; - - const refreshContextResponse = Object.assign({}, getContextResponse, { - experiments: [ - { - id: 6, - name: "exp_test_new", - iteration: 2, - unitType: "session_id", - seedHi: 934590467, - seedLo: 714771373, - split: [0.5, 0.5], - trafficSeedHi: 940553836, - trafficSeedLo: 270705624, - trafficSplit: [0.0, 1.0], - fullOnVariant: 1, - applications: [ - { - name: "website", - }, - ], - variants: [ - { - name: "A", - config: null, - }, - { - name: "B", - config: '{"show-modal":true}', - }, - ], - }, - ].concat(getContextResponse.experiments), - }); - - const audienceContextResponse = { - ...getContextResponse, - experiments: getContextResponse.experiments.map((x) => { - if (x.name === "exp_test_ab") { - return { - ...x, - audience: JSON.stringify({ - filter: [{ gte: [{ var: "age" }, { value: 20 }] }], - }), - }; - } - return x; - }), - }; - - const audienceStrictContextResponse = { - ...audienceContextResponse, - experiments: audienceContextResponse.experiments.map((x) => { - if (x.name === "exp_test_ab") { - return { - ...x, - audienceStrict: true, - variants: x.variants.map((v) => { - if (v.name === "A") { - return { name: "A", config: '{"banner.size":"tiny"}' }; - } - return v; - }), - }; - } - return x; - }), - }; - - const expectedVariants = { - exp_test_ab: 1, - exp_test_abc: 2, - exp_test_not_eligible: 0, - exp_test_fullon: 2, - exp_test_new: 1, - exp_test_custom_fields: 1, - }; - - const lowestIdConflictingKeyContextResponse = { - ...getContextResponse, - experiments: getContextResponse.experiments.map((e) => { - if (e.name === "exp_test_ab") { - return { - ...e, - id: 99, - variants: e.variants.map((v, i) => { - if (i === expectedVariants[e.name]) { - return { - ...v, - config: JSON.stringify({ - icon: "arrow", - }), - }; - } - return v; - }), - }; - } - if (e.name === "exp_test_abc") { - return { - ...e, - id: 1, - variants: e.variants.map((v, i) => { - if (i === expectedVariants[e.name]) { - return { - ...v, - config: JSON.stringify({ icon: "circle" }), - }; - } - return v; - }), - }; - } - return e; - }), - }; - - const disjointedContextResponse = { - ...getContextResponse, - experiments: getContextResponse.experiments.map((exp) => { - if (exp.name === "exp_test_ab") { - return { - ...exp, - audienceStrict: true, - audience: JSON.stringify({ - filter: [{ gte: [{ var: "age" }, { value: 20 }] }], - }), - variants: exp.variants.map((v, i) => { - if (i === expectedVariants[exp.name]) { - return { - ...v, - config: JSON.stringify({ - icon: "arrow", - }), - }; - } - return v; - }), - }; - } - if (exp.name === "exp_test_abc") { - return { - ...exp, - audienceStrict: true, - audience: JSON.stringify({ - filter: [{ lt: [{ var: "age" }, { value: 20 }] }], - }), - variants: exp.variants.map((variant, i) => { - if (i === expectedVariants[exp.name]) { - return { - ...variant, - config: JSON.stringify({ - icon: "circle", - }), - }; - } - return variant; - }), - }; - } - return exp; - }), - }; - - const expectedVariables = { - "banner.border": 1, - "banner.size": "large", - "button.color": "red", - "submit.color": "blue", - "submit.shape": "rect", - "show-modal": true, - "submit.size": "sm", - }; - - const variableExperiments = { - "banner.border": ["exp_test_ab"], - "banner.size": ["exp_test_ab"], - "button.color": ["exp_test_abc"], - "card.width": ["exp_test_not_eligible"], - "submit.color": ["exp_test_fullon"], - "submit.shape": ["exp_test_fullon"], - "submit.size": ["exp_test_custom_fields"], - "show-modal": ["exp_test_new"], - }; - - const sdk = new SDK(); - const client = new Client(); - const publisher = new ContextPublisher(); - const provider = new ContextDataProvider(); - - sdk.getContextDataProvider.mockReturnValue(provider); - sdk.getContextPublisher.mockReturnValue(publisher); - sdk.getClient.mockReturnValue(client); - sdk.getEventLogger.mockReturnValue(SDK.defaultEventLogger); - - client.getAgent.mockReturnValue("absmartly-javascript-sdk"); - client.getApplication.mockReturnValue({ name: "website", version: 0 }); - client.getEnvironment.mockReturnValue("production"); - - const contextOptions = { - publishDelay: -1, - refreshPeriod: 0, - }; - - const timeOrigin = 1611141535729; - - beforeEach(() => { - jest.spyOn(Date, "now").mockImplementation(() => timeOrigin); - }); - - describe("Context", () => { - it("should be ready with data", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.isReady()).toEqual(true); - expect(context.isFailed()).toEqual(false); - - context.ready().then(() => { - expect(context.isReady()).toEqual(true); - expect(context.data()).toStrictEqual(getContextResponse); - expect(context.eventLogger()).toBe(SDK.defaultEventLogger); - expect(context.provider()).toBe(provider); - expect(context.publisher()).toBe(publisher); - - done(); - }); - }); - - it("should use custom publisher, dataProvider and eventLogger", (done) => { - const customPublisher = new ContextPublisher(); - const customDataProvider = new ContextDataProvider(); - const customEventLogger = jest.fn(); - - const context = new Context( - sdk, - { - ...contextOptions, - publisher: customPublisher, - dataProvider: customDataProvider, - eventLogger: customEventLogger, - }, - contextParams, - getContextResponse - ); - expect(context.isReady()).toEqual(true); - expect(context.isFailed()).toEqual(false); - - context.ready().then(() => { - expect(context.isReady()).toEqual(true); - expect(context.data()).toStrictEqual(getContextResponse); - expect(context.eventLogger()).toBe(customEventLogger); - expect(context.provider()).toBe(customDataProvider); - expect(context.publisher()).toBe(customPublisher); - - done(); - }); - }); - - it("should become ready and call handler", (done) => { - const context = new Context(sdk, contextOptions, contextParams, Promise.resolve(getContextResponse)); - expect(context.isReady()).toEqual(false); - expect(context.isFailed()).toEqual(false); - - context.ready().then(() => { - expect(context.isReady()).toEqual(true); - expect(context.data()).toStrictEqual(getContextResponse); - expect(context.eventLogger()).toBe(SDK.defaultEventLogger); - expect(context.provider()).toBe(provider); - expect(context.publisher()).toBe(publisher); - - done(); - }); - }); - - it("should become ready and failed, and call handler on failure", (done) => { - const context = new Context(sdk, contextOptions, contextParams, Promise.reject("bad request error text")); - expect(context.isReady()).toEqual(false); - expect(context.isFailed()).toEqual(false); - - context.ready().then(() => { - expect(context.isReady()).toEqual(true); - expect(context.isFailed()).toEqual(true); - expect(context.data()).toStrictEqual({}); - expect(context.eventLogger()).toBe(SDK.defaultEventLogger); - expect(context.provider()).toBe(provider); - expect(context.publisher()).toBe(publisher); - - done(); - }); - }); - - it("should call event logger on error", (done) => { - SDK.defaultEventLogger.mockClear(); - - const context = new Context(sdk, contextOptions, contextParams, Promise.reject("bad request error text")); - context.ready().then(() => { - expect(SDK.defaultEventLogger).toHaveBeenCalledTimes(1); - expect(SDK.defaultEventLogger).toHaveBeenCalledWith(context, "error", "bad request error text"); - - done(); - }); - }); - - it("should call event logger on success", (done) => { - SDK.defaultEventLogger.mockClear(); - - const context = new Context(sdk, contextOptions, contextParams, Promise.resolve(getContextResponse)); - context.ready().then(() => { - expect(SDK.defaultEventLogger).toHaveBeenCalledTimes(1); - expect(SDK.defaultEventLogger).toHaveBeenCalledWith(context, "ready", getContextResponse); - - done(); - }); - }); - - it("should call event logger on pre-fetched experiment data", (done) => { - SDK.defaultEventLogger.mockClear(); - - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - context.ready().then(() => { - expect(SDK.defaultEventLogger).toHaveBeenCalledTimes(1); - expect(SDK.defaultEventLogger).toHaveBeenCalledWith(context, "ready", getContextResponse); - - done(); - }); - }); - - it("should throw when not ready", (done) => { - const context = new Context(sdk, contextOptions, contextParams, Promise.resolve(getContextResponse)); - expect(context.isReady()).toEqual(false); - expect(context.isFailed()).toEqual(false); - expect(context.isFinalized()).toEqual(false); - - expect(() => context.data()).toThrow(); - expect(() => context.treatment("test")).toThrow(); - expect(() => context.peek("test")).toThrow(); - expect(() => context.experiments()).toThrow(); - expect(() => context.variableKeys()).toThrow(); - expect(() => context.variableValue("a", 17)).toThrow(); - expect(() => context.peekVariableValue("a", 17)).toThrow(); - - done(); - }); - - it("should load experiment data", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - - expect(context.experiments()).toEqual(getContextResponse.experiments.map((x) => x.name)); - for (const experiment of getContextResponse.experiments) { - expect(context.peek(experiment.name)).toEqual(expectedVariants[experiment.name]); - expect(context.treatment(experiment.name)).toEqual(expectedVariants[experiment.name]); - } - expect(context.data()).toEqual(getContextResponse); - - done(); - }); - - it("should start refresh timer after ready", (done) => { - jest.useFakeTimers("legacy"); - jest.spyOn(global, "setInterval"); - - const refreshPeriod = 1000; - const context = new Context( - sdk, - Object.assign(contextOptions, { refreshPeriod }), - contextParams, - Promise.resolve(getContextResponse) - ); - - expect(context.isReady()).toEqual(false); - expect(context.isFailed()).toEqual(false); - - expect(setInterval).not.toHaveBeenCalled(); - - context.ready().then(() => { - expect(setInterval).toHaveBeenCalledTimes(1); - expect(setInterval).toHaveBeenCalledWith(expect.anything(), refreshPeriod); - setInterval.mockClear(); - - jest.advanceTimersByTime(refreshPeriod - 1); - - expect(provider.getContextData).not.toHaveBeenCalled(); - - const getContextPromise = Promise.resolve(refreshContextResponse); - provider.getContextData.mockReturnValue(getContextPromise); - - jest.advanceTimersByTime(refreshPeriod); - - getContextPromise.then(() => { - expect(setInterval).not.toHaveBeenCalled(); - expect(provider.getContextData).toHaveBeenCalledTimes(1); - expect(provider.getContextData).toHaveBeenCalledWith(sdk, undefined); - provider.getContextData.mockClear(); - - // test another interval - const nextGetContextPromise = Promise.resolve(refreshContextResponse); - provider.getContextData.mockReturnValue(nextGetContextPromise); - - jest.advanceTimersByTime(refreshPeriod); - nextGetContextPromise.then(() => { - expect(setInterval).not.toHaveBeenCalled(); - expect(provider.getContextData).toHaveBeenCalledTimes(1); - expect(provider.getContextData).toHaveBeenCalledWith(sdk, undefined); - - done(); - }); - }); - }); - }); - }); - - describe("unit()", () => { - it("should set a unit", (done) => { - const context = new Context(sdk, contextOptions, { units: {} }, getContextResponse); - - context.units(units); - - for (const [key, value] of Object.entries(units)) { - expect(context.getUnit(key)).toEqual(value); - } - - expect(context.getUnits()).toEqual(units); - - done(); - }); - it("should throw on duplicate unit type set", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.isReady()).toEqual(true); - - expect(() => context.unit("session_id", "new_id")).toThrow(); - - // should not throw if set to the same value - expect(() => context.unit("session_id", "e791e240fcd3df7d238cfc285f475e8152fcc0ec")).not.toThrow(); - - done(); - }); - - it("should throw on invalid uid", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.isReady()).toEqual(true); - - expect(() => context.unit("session_id", "")).toThrow(); - expect(() => context.unit("session_id", null)).toThrow(); - expect(() => context.unit("session_id", undefined)).toThrow(); - expect(() => context.unit("session_id", true)).toThrow(); - expect(() => context.unit("session_id", {})).toThrow(); - expect(() => context.unit("session_id", [])).toThrow(); - - done(); - }); - - it("should be callable before ready()", (done) => { - const context = new Context(sdk, contextOptions, {}, Promise.resolve(getContextResponse)); - - context.units(contextParams.units); - - context.ready().then(() => { - expect(context.isReady()).toEqual(true); - - context.treatment("exp_test_ab"); - - publisher.publish.mockReturnValue(Promise.resolve()); - - jest.spyOn(Date, "now").mockImplementation(() => timeOrigin + 100); - - context.publish().then(() => { - expect(publisher.publish).toHaveBeenCalledTimes(1); - expect(publisher.publish).toHaveBeenCalledWith( - { - publishedAt: 1611141535829, - units: publishUnits, - hashed: true, - sdkVersion: SDK_VERSION, - exposures: [ - { - id: 1, - name: "exp_test_ab", - unit: "session_id", - exposedAt: 1611141535729, - variant: 1, - assigned: true, - eligible: true, - overridden: false, - fullOn: false, - custom: false, - audienceMismatch: false, - }, - ], - }, - sdk, - context, - undefined - ); - - done(); - }); - }); - }); - - it("should throw after finalized() call", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - publisher.publish.mockReturnValue(Promise.resolve()); - - context.treatment("exp_test_ab"); - - expect(context.pending()).toEqual(1); - - context.finalize().then(() => { - expect(() => context.unit("test", "test")).toThrow(); // finalizing - - done(); - }); - - expect(context.isFinalizing()).toEqual(true); - expect(() => context.unit("test", "test")).toThrow(); // finalizing - }); - }); - - describe("getAttribute()", () => { - it("should get the last set attribute", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - - context.attribute("attr1", "value1"); - context.attribute("attr1", "value2"); - - expect(context.getAttribute("attr1")).toEqual("value2"); - - done(); - }); - }); - - describe("attribute()", () => { - it("should set an attribute", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - - context.attribute("attr1", "value1"); - context.attributes({ - attr2: "value2", - attr3: 15, - }); - - expect(context.getAttribute("attr1")).toEqual("value1"); - expect(context.getAttributes()).toEqual({ - attr1: "value1", - attr2: "value2", - attr3: 15, - }); - - done(); - }); - - it("should be callable before ready()", (done) => { - const context = new Context(sdk, contextOptions, contextParams, Promise.resolve(getContextResponse)); - expect(context.isReady()).toEqual(false); - expect(context.isFailed()).toEqual(false); - expect(context.isFinalized()).toEqual(false); - - context.attribute("attr1", "value1"); - context.attributes({ - attr2: "value2", - attr3: 3, - }); - - expect(context.getAttribute("attr1")).toEqual("value1"); - expect(context.getAttributes()).toEqual({ - attr1: "value1", - attr2: "value2", - attr3: 3, - }); - - context.ready().then(() => { - expect(context.isReady()).toEqual(true); - - context.treatment("exp_test_ab"); - - publisher.publish.mockReturnValue(Promise.resolve()); - - jest.spyOn(Date, "now").mockImplementation(() => timeOrigin + 100); - - context.publish().then(() => { - expect(publisher.publish).toHaveBeenCalledTimes(1); - expect(publisher.publish).toHaveBeenCalledWith( - { - publishedAt: 1611141535829, - units: publishUnits, - hashed: true, - sdkVersion: SDK_VERSION, - exposures: [ - { - id: 1, - name: "exp_test_ab", - unit: "session_id", - exposedAt: 1611141535729, - variant: 1, - assigned: true, - eligible: true, - overridden: false, - fullOn: false, - custom: false, - audienceMismatch: false, - }, - ], - attributes: [ - { - name: "attr1", - setAt: 1611141535729, - value: "value1", - }, - { - name: "attr2", - setAt: 1611141535729, - value: "value2", - }, - { - name: "attr3", - setAt: 1611141535729, - value: 3, - }, - ], - }, - sdk, - context, - undefined - ); - - done(); - }); - }); - }); - }); - - describe("refresh()", () => { - it("should call client and load new data", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - - provider.getContextData.mockReturnValue(Promise.resolve(refreshContextResponse)); - - context.refresh().then(() => { - expect(provider.getContextData).toHaveBeenCalledTimes(1); - expect(provider.getContextData).toHaveBeenCalledWith(sdk, undefined); - - expect(context.experiments()).toEqual(refreshContextResponse.experiments.map((x) => x.name)); - for (const experiment of refreshContextResponse.experiments) { - expect(context.treatment(experiment.name)).toEqual(expectedVariants[experiment.name]); - } - expect(context.data()).toEqual(refreshContextResponse); - - done(); - }); - }); - - it("should pass through request options", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - - provider.getContextData.mockReturnValue(Promise.resolve(refreshContextResponse)); - - context.refresh({ timeout: 1234 }).then(() => { - expect(provider.getContextData).toHaveBeenCalledTimes(1); - expect(provider.getContextData).toHaveBeenCalledWith(sdk, { timeout: 1234 }); - - expect(context.experiments()).toEqual(refreshContextResponse.experiments.map((x) => x.name)); - for (const experiment of refreshContextResponse.experiments) { - expect(context.treatment(experiment.name)).toEqual(expectedVariants[experiment.name]); - } - expect(context.data()).toEqual(refreshContextResponse); - - done(); - }); - }); - - it("should reject promise on error", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - - provider.getContextData.mockReturnValueOnce(Promise.reject(new Error("test error"))); - - context.refresh().catch((error) => { - expect(error.message).toEqual("test error"); - done(); - }); - }); - - it("should not re-queue exposures after refresh when not changed", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - - for (const experiment of getContextResponse.experiments) { - context.treatment(experiment.name); - } - - expect(context.pending()).toEqual(getContextResponse.experiments.length); - - provider.getContextData.mockReturnValue(Promise.resolve(refreshContextResponse)); - - context.refresh().then(() => { - expect(context.pending()).toEqual(getContextResponse.experiments.length); - - expect(provider.getContextData).toHaveBeenCalledTimes(1); - expect(provider.getContextData).toHaveBeenCalledWith(sdk, undefined); - - for (const experiment of getContextResponse.experiments) { - context.treatment(experiment.name); - } - - expect(context.pending()).toEqual(getContextResponse.experiments.length); - - for (const experiment of refreshContextResponse.experiments) { - context.treatment(experiment.name); - } - - expect(context.pending()).toEqual(refreshContextResponse.experiments.length); - - done(); - }); - }); - - it("should not re-queue when not changed on audience mismatch", (done) => { - const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse); - - expect(context.treatment("exp_test_ab")).toEqual(0); - - expect(context.pending()).toEqual(1); - - provider.getContextData.mockReturnValue(Promise.resolve(audienceStrictContextResponse)); - - context.refresh().then(() => { - expect(context.treatment("exp_test_ab")).toEqual(0); - - expect(context.pending()).toEqual(1); - - done(); - }); - }); - - it("should not re-queue when not changed with override", (done) => { - const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse); - - context.override("exp_test_ab", 3); - expect(context.treatment("exp_test_ab")).toEqual(3); - - expect(context.pending()).toEqual(1); - - provider.getContextData.mockReturnValue(Promise.resolve(audienceStrictContextResponse)); - - context.refresh().then(() => { - expect(context.treatment("exp_test_ab")).toEqual(3); - - expect(context.pending()).toEqual(1); - - done(); - }); - }); - - it("should not call client publish when failed", (done) => { - const context = new Context(sdk, contextOptions, contextParams, Promise.reject("bad request error text")); - - context.ready().then(() => { - context.refresh().then(() => { - expect(provider.getContextData).not.toHaveBeenCalled(); - - done(); - }); - }); - }); - - it("should call event logger when failed", (done) => { - const context = new Context(sdk, contextOptions, contextParams, Promise.resolve(getContextResponse)); - context.ready().then(() => { - provider.getContextData.mockReturnValueOnce(Promise.reject(new Error("test error"))); - - SDK.defaultEventLogger.mockClear(); - context.refresh().catch((error) => { - expect(SDK.defaultEventLogger).toHaveBeenCalledTimes(1); - expect(SDK.defaultEventLogger).toHaveBeenCalledWith(context, "error", error); - - done(); - }); - }); - }); - - it("should call event logger on success", (done) => { - const context = new Context(sdk, contextOptions, contextParams, Promise.resolve(getContextResponse)); - - provider.getContextData.mockReturnValueOnce(Promise.resolve(refreshContextResponse)); - - context.ready().then(() => { - SDK.defaultEventLogger.mockClear(); - context.refresh().then(() => { - expect(SDK.defaultEventLogger).toHaveBeenCalledTimes(1); - expect(SDK.defaultEventLogger).toHaveBeenCalledWith(context, "refresh", refreshContextResponse); - - done(); - }); - }); - }); - - it("should throw after finalized() call", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - publisher.publish.mockReturnValue(Promise.resolve()); - - context.treatment("exp_test_ab"); - - expect(context.pending()).toEqual(1); - - context.finalize().then(() => { - expect(() => context.refresh()).toThrow(); - - done(); - }); - - expect(context.isFinalizing()).toEqual(true); - expect(() => context.refresh()).toThrow(); // finalizing - }); - - it("should keep overrides", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - - provider.getContextData.mockReturnValue(Promise.resolve(refreshContextResponse)); - - context.override("not_found", 3); - expect(context.peek("not_found")).toEqual(3); - - context.refresh().then(() => { - expect(context.peek("not_found")).toEqual(3); - - done(); - }); - }); - - it("should keep custom assignments", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - - provider.getContextData.mockReturnValue(Promise.resolve(refreshContextResponse)); - - context.customAssignment("exp_test_ab", 3); - - expect(context.peek("exp_test_ab")).toEqual(3); - - context.refresh().then(() => { - expect(context.peek("exp_test_ab")).toEqual(3); - - done(); - }); - }); - - it("should pick up changes in experiment stopped", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - const experimentName = "exp_test_abc"; - - expect(context.treatment(experimentName)).toEqual(expectedVariants[experimentName]); - expect(context.pending()).toEqual(1); - - const stoppedRefreshContextResponse = clone(getContextResponse); - stoppedRefreshContextResponse.experiments = stoppedRefreshContextResponse.experiments.filter( - (x) => x.name !== experimentName - ); - - provider.getContextData.mockReturnValue(Promise.resolve(stoppedRefreshContextResponse)); - - context.refresh().then(() => { - expect(context.treatment(experimentName)).toEqual(0); - expect(context.pending()).toEqual(2); // exposure before the change + exposure after stopped - - done(); - }); - }); - - it("should pick up changes in experiment started", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - const experimentName = "exp_test_new"; - - expect(context.treatment(experimentName)).toEqual(0); - expect(context.pending()).toEqual(1); - - provider.getContextData.mockReturnValue(Promise.resolve(refreshContextResponse)); - - context.refresh().then(() => { - expect(context.treatment(experimentName)).toEqual(1); - expect(context.pending()).toEqual(2); // exposure before the change + exposure after stopped - - done(); - }); - }); - - it("should pick up changes in experiment fullon", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - const experimentName = "exp_test_abc"; - - expect(context.treatment(experimentName)).toEqual(expectedVariants[experimentName]); - expect(context.pending()).toEqual(1); - - const fullOnRefreshContextResponse = clone(getContextResponse); - for (const experiment of fullOnRefreshContextResponse.experiments) { - if (experiment.name === experimentName) { - expect(experiment.fullOnVariant).toEqual(0); - experiment.fullOnVariant = 1; - expect(expectedVariants[experimentName]).not.toEqual(experiment.fullOnVariant); - } - } - - provider.getContextData.mockReturnValue(Promise.resolve(fullOnRefreshContextResponse)); - - context.refresh().then(() => { - expect(context.treatment(experimentName)).toEqual(1); - expect(context.pending()).toEqual(2); // exposure before the change + exposure after fullon - - done(); - }); - }); - - it("should pick up changes in experiment traffic split", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - const experimentName = "exp_test_not_eligible"; - - expect(context.treatment(experimentName)).toEqual(expectedVariants[experimentName]); - expect(context.pending()).toEqual(1); - - const scaledUpRefreshContextResponse = clone(getContextResponse); - for (const experiment of scaledUpRefreshContextResponse.experiments) { - if (experiment.name === experimentName) { - experiment.trafficSplit = [0.0, 1.0]; - } - } - - provider.getContextData.mockReturnValue(Promise.resolve(scaledUpRefreshContextResponse)); - - context.refresh().then(() => { - expect(context.treatment(experimentName)).toEqual(2); - expect(context.pending()).toEqual(2); // exposure before the change + exposure after fullon - - done(); - }); - }); - - it("should pick up changes in experiment iteration", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - const experimentName = "exp_test_abc"; - - expect(context.treatment(experimentName)).toEqual(expectedVariants[experimentName]); - expect(context.pending()).toEqual(1); - - const iteratedRefreshContextResponse = clone(getContextResponse); - for (const experiment of iteratedRefreshContextResponse.experiments) { - if (experiment.name === experimentName) { - experiment.iteration = 2; - experiment.trafficSeedHi = 54870830; - experiment.trafficSeedHi = 398724581; - experiment.seedHi = 77498863; - experiment.seedHi = 34737352; - } - } - - provider.getContextData.mockReturnValue(Promise.resolve(iteratedRefreshContextResponse)); - - context.refresh().then(() => { - expect(context.treatment(experimentName)).toEqual(1); - expect(context.pending()).toEqual(2); // exposure before the change + exposure after fullon - - done(); - }); - }); - - it("should pick up changes in experiment id", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - const experimentName = "exp_test_abc"; - - expect(context.treatment(experimentName)).toEqual(expectedVariants[experimentName]); - expect(context.pending()).toEqual(1); - - const iteratedRefreshContextResponse = clone(getContextResponse); - for (const experiment of iteratedRefreshContextResponse.experiments) { - if (experiment.name === experimentName) { - experiment.id = 11; - experiment.trafficSeedHi = 54870830; - experiment.trafficSeedHi = 398724581; - experiment.seedHi = 77498863; - experiment.seedHi = 34737352; - } - } - - provider.getContextData.mockReturnValue(Promise.resolve(iteratedRefreshContextResponse)); - - context.refresh().then(() => { - expect(context.treatment(experimentName)).toEqual(1); - expect(context.pending()).toEqual(2); // exposure before the change + exposure after fullon - - done(); - }); - }); - }); - - describe("peek()", () => { - it("should not queue exposures", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - for (const experiment of getContextResponse.experiments) { - expect(context.peek(experiment.name)).toEqual(expectedVariants[experiment.name]); - } - - expect(context.pending()).toEqual(0); - - done(); - }); - - it("should return override variant", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - for (const experiment of getContextResponse.experiments) { - context.override(experiment.name, expectedVariants[experiment.name] + 11); - } - context.override("not_found", 3); - - for (const experiment of getContextResponse.experiments) { - expect(context.peek(experiment.name)).toEqual(expectedVariants[experiment.name] + 11); - } - - expect(context.peek("not_found")).toEqual(3); - - expect(context.pending()).toEqual(0); - - done(); - }); - - it("should return assigned variant on audience mismatch in non-strict mode", (done) => { - const context = new Context(sdk, contextOptions, contextParams, audienceContextResponse); - - expect(context.peek("exp_test_ab")).toEqual(1); - - done(); - }); - - it("should return control variant on audience mismatch in strict mode", (done) => { - const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse); - - expect(context.peek("exp_test_ab")).toEqual(0); - - done(); - }); - - it("should re-evaluate audience expression when attributes change in strict mode", (done) => { - const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse); - - // First peek call without matching attribute should return control (0) - expect(context.peek("exp_test_ab")).toEqual(0); - - // Set attribute that matches the audience filter (age >= 20) - context.attribute("age", 25); - - // Second peek call should re-evaluate and return assigned variant (1) - expect(context.peek("exp_test_ab")).toEqual(1); - - // peek() should not queue exposures - expect(context.pending()).toEqual(0); - - done(); - }); - - it("should re-evaluate audience expression when attributes change in non-strict mode", (done) => { - const context = new Context(sdk, contextOptions, contextParams, audienceContextResponse); - - // First peek call without matching attribute should return assigned variant (1) - expect(context.peek("exp_test_ab")).toEqual(1); - - // Set attribute that matches the audience filter (age >= 20) - context.attribute("age", 25); - - // Second peek call should re-evaluate and still return 1 - expect(context.peek("exp_test_ab")).toEqual(1); - - // peek() should not queue exposures - expect(context.pending()).toEqual(0); - - done(); - }); - - it("should not re-evaluate audience when no new attributes set", (done) => { - const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse); - - // Set attribute first - context.attribute("age", 15); - - // First peek call with non-matching attribute should return control (0) - expect(context.peek("exp_test_ab")).toEqual(0); - - // Second peek call without adding new attributes should use cached assignment - expect(context.peek("exp_test_ab")).toEqual(0); - - // peek() should not queue exposures - expect(context.pending()).toEqual(0); - - done(); - }); - }); - - describe("treatment()", () => { - it("should queue exposures", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - for (const experiment of getContextResponse.experiments) { - context.treatment(experiment.name); - } - - expect(context.pending()).toEqual(getContextResponse.experiments.length); - - for (const experiment of getContextResponse.experiments) { - context.treatment(experiment.name); - } - - expect(context.pending()).toEqual(getContextResponse.experiments.length); - - publisher.publish.mockReturnValue(Promise.resolve()); - - context.publish().then(() => { - expect(publisher.publish).toHaveBeenCalledWith( - { - publishedAt: 1611141535729, - units: publishUnits, - hashed: true, - sdkVersion: SDK_VERSION, - exposures: [ - { - id: 1, - assigned: true, - eligible: true, - exposedAt: 1611141535729, - name: "exp_test_ab", - overridden: false, - unit: "session_id", - variant: 1, - fullOn: false, - custom: false, - audienceMismatch: false, - }, - { - id: 2, - assigned: true, - eligible: true, - exposedAt: 1611141535729, - name: "exp_test_abc", - overridden: false, - unit: "session_id", - variant: 2, - fullOn: false, - custom: false, - audienceMismatch: false, - }, - { - id: 3, - assigned: true, - eligible: false, - exposedAt: 1611141535729, - name: "exp_test_not_eligible", - overridden: false, - unit: "user_id", - variant: 0, - fullOn: false, - custom: false, - audienceMismatch: false, - }, - { - id: 4, - assigned: true, - eligible: true, - exposedAt: 1611141535729, - name: "exp_test_fullon", - overridden: false, - unit: "session_id", - variant: 2, - fullOn: true, - custom: false, - audienceMismatch: false, - }, - { - id: 5, - assigned: true, - eligible: true, - exposedAt: 1611141535729, - name: "exp_test_custom_fields", - overridden: false, - unit: "session_id", - variant: 1, - fullOn: false, - custom: false, - audienceMismatch: false, - }, - ], - }, - sdk, - context, - undefined - ); - - done(); - }); - }); - - it("should queue exposures after peek()", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - for (const experiment of getContextResponse.experiments) { - context.peek(experiment.name); - } - - expect(context.pending()).toEqual(0); - - for (const experiment of getContextResponse.experiments) { - context.treatment(experiment.name); - } - - expect(context.pending()).toEqual(getContextResponse.experiments.length); - - done(); - }); - - it("should queue exposures only once", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - for (const experiment of getContextResponse.experiments) { - context.treatment(experiment.name); - } - - expect(context.pending()).toEqual(getContextResponse.experiments.length); - - for (const experiment of getContextResponse.experiments) { - context.treatment(experiment.name); - } - - expect(context.pending()).toEqual(getContextResponse.experiments.length); - - done(); - }); - - it("should call event logger", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - for (const experiment of getContextResponse.experiments) { - SDK.defaultEventLogger.mockClear(); - context.treatment(experiment.name); - expect(SDK.defaultEventLogger).toHaveBeenCalledTimes(1); - expect(SDK.defaultEventLogger).toHaveBeenCalledWith(context, "exposure", { - exposedAt: timeOrigin, - eligible: experiment.name !== "exp_test_not_eligible", - assigned: true, - overridden: false, - id: experiment.id, - name: experiment.name, - unit: experiment.unitType, - variant: expectedVariants[experiment.name], - fullOn: experiment.name === "exp_test_fullon", - custom: false, - audienceMismatch: false, - }); - } - - // check it calls logger only once - for (const experiment of getContextResponse.experiments) { - SDK.defaultEventLogger.mockClear(); - context.treatment(experiment.name); - expect(SDK.defaultEventLogger).not.toHaveBeenCalled(); - } - - done(); - }); - - it("should queue exposure with base variant on unknown/stopped experiment", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - expect(context.treatment("not_found")).toEqual(0); - - expect(context.pending()).toEqual(1); - - publisher.publish.mockReturnValue(Promise.resolve()); - - context.publish().then(() => { - expect(publisher.publish).toHaveBeenCalledWith( - { - publishedAt: 1611141535729, - units: publishUnits, - hashed: true, - sdkVersion: SDK_VERSION, - exposures: [ - { - id: 0, - assigned: false, - eligible: true, - exposedAt: 1611141535729, - name: "not_found", - overridden: false, - unit: null, - variant: 0, - fullOn: false, - custom: false, - audienceMismatch: false, - }, - ], - }, - sdk, - context, - undefined - ); - - done(); - }); - }); - - it("should queue exposure with audienceMatch true on audience match", (done) => { - const context = new Context(sdk, contextOptions, contextParams, audienceContextResponse); - context.attribute("age", 21); - - expect(context.treatment("exp_test_ab")).toEqual(1); - - expect(context.pending()).toEqual(1); - - publisher.publish.mockReturnValue(Promise.resolve()); - - context.publish().then(() => { - expect(publisher.publish).toHaveBeenCalledWith( - { - publishedAt: 1611141535729, - units: publishUnits, - hashed: true, - sdkVersion: SDK_VERSION, - attributes: [ - { - name: "age", - setAt: 1611141535729, - value: 21, - }, - ], - exposures: [ - { - id: 1, - assigned: true, - eligible: true, - exposedAt: 1611141535729, - name: "exp_test_ab", - overridden: false, - unit: "session_id", - variant: 1, - fullOn: false, - custom: false, - audienceMismatch: false, - }, - ], - }, - sdk, - context, - undefined - ); - - done(); - }); - }); - - it("should queue exposure with audienceMatch false on audience mismatch", (done) => { - const context = new Context(sdk, contextOptions, contextParams, audienceContextResponse); - - expect(context.treatment("exp_test_ab")).toEqual(1); - - expect(context.pending()).toEqual(1); - - publisher.publish.mockReturnValue(Promise.resolve()); - - context.publish().then(() => { - expect(publisher.publish).toHaveBeenCalledWith( - { - publishedAt: 1611141535729, - units: publishUnits, - hashed: true, - sdkVersion: SDK_VERSION, - exposures: [ - { - id: 1, - assigned: true, - eligible: true, - exposedAt: 1611141535729, - name: "exp_test_ab", - overridden: false, - unit: "session_id", - variant: 1, - fullOn: false, - custom: false, - audienceMismatch: true, - }, - ], - }, - sdk, - context, - undefined - ); - - done(); - }); - }); - - it("should queue exposure with audienceMatch false and control variant on audience mismatch in strict mode", (done) => { - const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse); - - expect(context.treatment("exp_test_ab")).toEqual(0); - - expect(context.pending()).toEqual(1); - - publisher.publish.mockReturnValue(Promise.resolve()); - - context.publish().then(() => { - expect(publisher.publish).toHaveBeenCalledWith( - { - publishedAt: 1611141535729, - units: publishUnits, - hashed: true, - sdkVersion: SDK_VERSION, - exposures: [ - { - id: 1, - assigned: false, - eligible: true, - exposedAt: 1611141535729, - name: "exp_test_ab", - overridden: false, - unit: "session_id", - variant: 0, - fullOn: false, - custom: false, - audienceMismatch: true, - }, - ], - }, - sdk, - context, - undefined - ); - - done(); - }); - }); - - it("should not re-queue exposure on unknown experiment", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - - expect(context.pending()).toEqual(0); - - expect(context.treatment("not_found")).toEqual(0); - - expect(context.pending()).toEqual(1); - - expect(context.treatment("not_found")).toEqual(0); - - expect(context.pending()).toEqual(1); - - done(); - }); - - it("should queue exposure with override variant", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - context.override("exp_test_ab", 5); - context.override("not_found", 3); - - expect(context.treatment("exp_test_ab")).toEqual(5); - expect(context.treatment("not_found")).toEqual(3); - - expect(context.pending()).toEqual(2); - - publisher.publish.mockReturnValue(Promise.resolve()); - - context.publish().then(() => { - expect(publisher.publish).toHaveBeenCalledWith( - { - publishedAt: 1611141535729, - units: publishUnits, - hashed: true, - sdkVersion: SDK_VERSION, - exposures: [ - { - id: 1, - assigned: false, - eligible: true, - exposedAt: 1611141535729, - name: "exp_test_ab", - overridden: true, - unit: "session_id", - variant: 5, - fullOn: false, - custom: false, - audienceMismatch: false, - }, - { - id: 0, - assigned: false, - eligible: true, - exposedAt: 1611141535729, - name: "not_found", - overridden: true, - unit: null, - variant: 3, - fullOn: false, - custom: false, - audienceMismatch: false, - }, - ], - }, - sdk, - context, - undefined - ); - - done(); - }); - }); - - it("should throw after finalized() call", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - publisher.publish.mockReturnValue(Promise.resolve()); - - context.treatment("exp_test_ab"); - - expect(context.pending()).toEqual(1); - - context.finalize().then(() => { - expect(() => context.treatment("exp_test_ab")).toThrow(); - - done(); - }); - - expect(context.isFinalizing()).toEqual(true); - expect(() => context.treatment("exp_test_ab")).toThrow(); - }); - - it("should re-evaluate audience expression when attributes change in strict mode", (done) => { - const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse); - - // First treatment call without matching attribute should return control (0) - expect(context.treatment("exp_test_ab")).toEqual(0); - expect(context.pending()).toEqual(1); - - // Set attribute that matches the audience filter (age >= 20) - context.attribute("age", 25); - - // Second treatment call should re-evaluate and return assigned variant (1) - expect(context.treatment("exp_test_ab")).toEqual(1); - - // Should queue another exposure - expect(context.pending()).toEqual(2); - - done(); - }); - - it("should re-evaluate audience expression when attributes change in non-strict mode", (done) => { - const context = new Context(sdk, contextOptions, contextParams, audienceContextResponse); - - // First treatment call without matching attribute should return assigned variant (1) - // but with audienceMismatch = true - expect(context.treatment("exp_test_ab")).toEqual(1); - expect(context.pending()).toEqual(1); - - // Set attribute that matches the audience filter (age >= 20) - context.attribute("age", 25); - - // Second treatment call should re-evaluate - // The variant stays 1, but audienceMismatch should now be false - expect(context.treatment("exp_test_ab")).toEqual(1); - - // Should queue another exposure since audience result changed - expect(context.pending()).toEqual(2); - - done(); - }); - - it("should not re-evaluate audience when no new attributes set", (done) => { - const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse); - - // Set attribute first - context.attribute("age", 15); - - // First treatment call with non-matching attribute should return control (0) - expect(context.treatment("exp_test_ab")).toEqual(0); - expect(context.pending()).toEqual(1); - - // Second treatment call without adding new attributes should use cached assignment - expect(context.treatment("exp_test_ab")).toEqual(0); - - // Should not queue another exposure (uses cached assignment) - expect(context.pending()).toEqual(1); - - done(); - }); - - it("should not re-evaluate audience for experiments without audience filter", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - - // First treatment call - expect(context.treatment("exp_test_abc")).toEqual(2); - expect(context.pending()).toEqual(1); - - // Set an attribute - context.attribute("age", 25); - - // Second treatment call should use cached assignment since no audience filter - expect(context.treatment("exp_test_abc")).toEqual(2); - - // Should not queue another exposure - expect(context.pending()).toEqual(1); - - done(); - }); - - it("should re-evaluate from audience mismatch to match in strict mode", (done) => { - const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse); - - // First treatment call without attribute - audience mismatch, returns 0 - expect(context.treatment("exp_test_ab")).toEqual(0); - expect(context.pending()).toEqual(1); - - publisher.publish.mockReturnValue(Promise.resolve()); - - context.publish().then(() => { - // Verify first exposure had audienceMismatch = true - expect(publisher.publish).toHaveBeenCalledWith( - expect.objectContaining({ - exposures: [ - expect.objectContaining({ - name: "exp_test_ab", - variant: 0, - audienceMismatch: true, - assigned: false, - }), - ], - }), - sdk, - context, - undefined - ); - - // Set matching attribute - context.attribute("age", 30); - - // Second treatment should re-evaluate and return 1 - expect(context.treatment("exp_test_ab")).toEqual(1); - expect(context.pending()).toEqual(1); - - context.publish().then(() => { - // Verify second exposure had audienceMismatch = false - expect(publisher.publish).toHaveBeenCalledWith( - expect.objectContaining({ - exposures: [ - expect.objectContaining({ - name: "exp_test_ab", - variant: 1, - audienceMismatch: false, - assigned: true, - }), - ], - }), - sdk, - context, - undefined - ); - - done(); - }); - }); - }); - - it("should not re-evaluate when attribute set before assignment", (done) => { - const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse); - - // Set attribute before treatment call - context.attribute("age", 25); - - // First treatment call - attribute was already set, included in assignment - expect(context.treatment("exp_test_ab")).toEqual(1); - expect(context.pending()).toEqual(1); - - // Second treatment call should use cached assignment - expect(context.treatment("exp_test_ab")).toEqual(1); - - // Should not queue another exposure - expect(context.pending()).toEqual(1); - - done(); - }); - - it("should re-evaluate when attribute set after assignment", (done) => { - const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse); - - // First treatment call without attribute - audience mismatch, returns 0 - expect(context.treatment("exp_test_ab")).toEqual(0); - expect(context.pending()).toEqual(1); - - // Set attribute AFTER assignment was computed - context.attribute("age", 25); - - // Second treatment call should re-evaluate because attrsSeq increased - expect(context.treatment("exp_test_ab")).toEqual(1); - - // Should queue another exposure - expect(context.pending()).toEqual(2); - - done(); - }); - - it("should not invalidate cache when audience result unchanged after attribute change", (done) => { - const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse); - - // Set attribute that doesn't match (age < 20) - context.attribute("age", 15); - - // First treatment call - audience mismatch, returns 0 - expect(context.treatment("exp_test_ab")).toEqual(0); - expect(context.pending()).toEqual(1); - - // Set another attribute that still doesn't match (age < 20) - context.attribute("age", 18); - - // Second treatment call - audience result unchanged (still mismatch), should use cache - expect(context.treatment("exp_test_ab")).toEqual(0); - - // Should NOT queue another exposure since audience result didn't change - expect(context.pending()).toEqual(1); - - done(); - }); - - it("should update attrsSeq after checking unchanged audience to avoid repeated evaluation", (done) => { - const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse); - - // Set attribute that doesn't match (age < 20) - context.attribute("age", 15); - - // First treatment call - audience mismatch, returns 0 - expect(context.treatment("exp_test_ab")).toEqual(0); - expect(context.pending()).toEqual(1); - - // Set another attribute that still doesn't match - context.attribute("age", 16); - - // Second treatment - evaluates audience but result unchanged, updates attrsSeq - expect(context.treatment("exp_test_ab")).toEqual(0); - expect(context.pending()).toEqual(1); - - // Set yet another attribute that still doesn't match - context.attribute("age", 17); - - // Third treatment - evaluates audience but result unchanged, updates attrsSeq - expect(context.treatment("exp_test_ab")).toEqual(0); - expect(context.pending()).toEqual(1); - - // No new attributes set - // Fourth treatment - should use cache without re-evaluating - expect(context.treatment("exp_test_ab")).toEqual(0); - expect(context.pending()).toEqual(1); - - done(); - }); - }); - - describe("variableValue()", () => { - it("should not return variable values when unassigned", (done) => { - const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse); - - expect(context.pending()).toEqual(0); - - expect(context.variableValue("banner.size", 17)).toEqual(17); - - done(); - }); - it("should return variable values when overridden", (done) => { - const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse); - - expect(context.pending()).toEqual(0); - - context.override("exp_test_ab", 0); - - expect(context.variableValue("banner.size", 17)).toEqual("tiny"); - - done(); - }); - it("conflicting key disjoint audiences", (done) => { - const context1 = new Context(sdk, contextOptions, contextParams, disjointedContextResponse); - const context2 = new Context(sdk, contextOptions, contextParams, disjointedContextResponse); - - expect(context1.pending()).toEqual(0); - expect(context2.pending()).toEqual(0); - - expect(expectedVariants["exp_test_ab"]).not.toEqual(0); - expect(expectedVariants["exp_test_abc"]).not.toEqual(0); - - context1.attribute("age", 20); - expect(context1.variableValue("icon", "square")).toEqual("arrow"); - - context2.attribute("age", 19); - expect(context2.variableValue("icon", "square")).toEqual("circle"); - - done(); - }); - it("should queue exposures", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - const experiments = context.experiments(); - - for (const [key, experimentNames] of Object.entries(variableExperiments)) { - const experimentName = experimentNames[0]; - const actual = context.variableValue(key, 17); - const eligible = experimentName !== "exp_test_not_eligible"; - - if (eligible && experiments.indexOf(experimentName) !== -1) { - expect(actual).toEqual(expectedVariables[key]); - } else { - expect(actual).toBe(17); - } - } - - expect(context.pending()).toEqual(getContextResponse.experiments.length); - - publisher.publish.mockReturnValue(Promise.resolve()); - - context.publish().then(() => { - expect(publisher.publish).toHaveBeenCalledWith( - { - publishedAt: 1611141535729, - units: publishUnits, - hashed: true, - sdkVersion: SDK_VERSION, - exposures: [ - { - id: 1, - assigned: true, - eligible: true, - exposedAt: 1611141535729, - name: "exp_test_ab", - overridden: false, - unit: "session_id", - variant: 1, - fullOn: false, - custom: false, - audienceMismatch: false, - }, - { - id: 2, - assigned: true, - eligible: true, - exposedAt: 1611141535729, - name: "exp_test_abc", - overridden: false, - unit: "session_id", - variant: 2, - fullOn: false, - custom: false, - audienceMismatch: false, - }, - { - id: 3, - assigned: true, - eligible: false, - exposedAt: 1611141535729, - name: "exp_test_not_eligible", - overridden: false, - unit: "user_id", - variant: 0, - fullOn: false, - custom: false, - audienceMismatch: false, - }, - { - id: 4, - assigned: true, - eligible: true, - exposedAt: 1611141535729, - name: "exp_test_fullon", - overridden: false, - unit: "session_id", - variant: 2, - fullOn: true, - custom: false, - audienceMismatch: false, - }, - { - id: 5, - assigned: true, - eligible: true, - exposedAt: 1611141535729, - name: "exp_test_custom_fields", - overridden: false, - unit: "session_id", - variant: 1, - fullOn: false, - custom: false, - audienceMismatch: false, - }, - ], - }, - sdk, - context, - undefined - ); - - done(); - }); - }); - - it("should queue exposures after peekVariable()", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - const experiments = context.experiments(); - - for (const [key, experimentNames] of Object.entries(variableExperiments)) { - const experimentName = experimentNames[0]; - const actual = context.peekVariableValue(key, 17); - const eligible = experimentName !== "exp_test_not_eligible"; - - if (eligible && experiments.indexOf(experimentName) !== -1) { - expect(actual).toEqual(expectedVariables[key]); - } else { - expect(actual).toBe(17); - } - } - - expect(context.pending()).toEqual(0); - - for (const [key, experimentNames] of Object.entries(variableExperiments)) { - const experimentName = experimentNames[0]; - const actual = context.variableValue(key, 17); - const eligible = experimentName !== "exp_test_not_eligible"; - - if (eligible && experiments.indexOf(experimentName) !== -1) { - expect(actual).toEqual(expectedVariables[key]); - } else { - expect(actual).toBe(17); - } - } - - expect(context.pending()).toEqual(getContextResponse.experiments.length); - - done(); - }); - - it("should queue exposures only once", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - const experiments = context.experiments(); - - for (const [key, experimentNames] of Object.entries(variableExperiments)) { - const experimentName = experimentNames[0]; - const actual = context.variableValue(key, 17); - const eligible = experimentName !== "exp_test_not_eligible"; - - if (eligible && experiments.indexOf(experimentName) !== -1) { - expect(actual).toEqual(expectedVariables[key]); - } else { - expect(actual).toBe(17); - } - } - - expect(context.pending()).toEqual(getContextResponse.experiments.length); - - for (const [key, experimentNames] of Object.entries(variableExperiments)) { - const experimentName = experimentNames[0]; - const actual = context.variableValue(key, 17); - const eligible = experimentName !== "exp_test_not_eligible"; - - if (eligible && experiments.indexOf(experimentName) !== -1) { - expect(actual).toEqual(expectedVariables[key]); - } else { - expect(actual).toBe(17); - } - } - - expect(context.pending()).toEqual(getContextResponse.experiments.length); - - done(); - }); - - it("should call event logger", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - const experiments = context.experiments(); - const exposed = {}; - - for (const [key, experimentNames] of Object.entries(variableExperiments)) { - const experimentName = experimentNames[0]; - SDK.defaultEventLogger.mockClear(); - context.variableValue(key, 17); - - if (experiments.indexOf(experimentName) !== -1) { - const experiment = getContextResponse.experiments.filter((x) => x.name === experimentName)[0]; - if (!(experimentName in exposed)) { - exposed[experimentName] = true; - expect(SDK.defaultEventLogger).toHaveBeenCalledTimes(1); - expect(SDK.defaultEventLogger).toHaveBeenCalledWith(context, "exposure", { - exposedAt: timeOrigin, - eligible: experiment.name !== "exp_test_not_eligible", - assigned: true, - overridden: false, - id: experiment.id, - name: experiment.name, - unit: experiment.unitType, - variant: expectedVariants[experiment.name], - fullOn: experiment.name === "exp_test_fullon", - custom: false, - audienceMismatch: false, - }); - } else { - expect(SDK.defaultEventLogger).not.toHaveBeenCalled(); - } - } - } - - // check it calls logger only once - for (const [key, experimentNames] of Object.entries(variableExperiments)) { - const experimentName = experimentNames[0]; - SDK.defaultEventLogger.mockClear(); - context.variableValue(key, 17); - if (experiments.indexOf(experimentName) !== -1) { - expect(SDK.defaultEventLogger).not.toHaveBeenCalled(); - } - } - - done(); - }); - - it("should return defaultValue on unknown variable", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - expect(context.variableValue("not.found", 17)).toBe(17); - - done(); - }); - - it("should queue exposure with audienceMatch true on audience match", (done) => { - const context = new Context(sdk, contextOptions, contextParams, audienceContextResponse); - context.attribute("age", 21); - - expect(context.variableValue("banner.size", "small")).toEqual("large"); - - expect(context.pending()).toEqual(1); - - publisher.publish.mockReturnValue(Promise.resolve()); - - context.publish().then(() => { - expect(publisher.publish).toHaveBeenCalledWith( - { - publishedAt: 1611141535729, - units: publishUnits, - hashed: true, - sdkVersion: SDK_VERSION, - attributes: [ - { - name: "age", - setAt: 1611141535729, - value: 21, - }, - ], - exposures: [ - { - id: 1, - assigned: true, - eligible: true, - exposedAt: 1611141535729, - name: "exp_test_ab", - overridden: false, - unit: "session_id", - variant: 1, - fullOn: false, - custom: false, - audienceMismatch: false, - }, - ], - }, - sdk, - context, - undefined - ); - - done(); - }); - }); - - it("should queue exposure with audienceMatch false on audience mismatch", (done) => { - const context = new Context(sdk, contextOptions, contextParams, audienceContextResponse); - - expect(context.variableValue("banner.size", "small")).toEqual("large"); - - expect(context.pending()).toEqual(1); - - publisher.publish.mockReturnValue(Promise.resolve()); - - context.publish().then(() => { - expect(publisher.publish).toHaveBeenCalledWith( - { - publishedAt: 1611141535729, - units: publishUnits, - hashed: true, - sdkVersion: SDK_VERSION, - exposures: [ - { - id: 1, - assigned: true, - eligible: true, - exposedAt: 1611141535729, - name: "exp_test_ab", - overridden: false, - unit: "session_id", - variant: 1, - fullOn: false, - custom: false, - audienceMismatch: true, - }, - ], - }, - sdk, - context, - undefined - ); - - done(); - }); - }); - - it("should queue exposure with audienceMatch false and control variant on audience mismatch in strict mode", (done) => { - const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse); - - expect(context.variableValue("banner.size", "small")).toEqual("small"); - - expect(context.pending()).toEqual(1); - - publisher.publish.mockReturnValue(Promise.resolve()); - - context.publish().then(() => { - expect(publisher.publish).toHaveBeenCalledWith( - { - publishedAt: 1611141535729, - units: publishUnits, - hashed: true, - sdkVersion: SDK_VERSION, - exposures: [ - { - id: 1, - assigned: false, - eligible: true, - exposedAt: 1611141535729, - name: "exp_test_ab", - overridden: false, - unit: "session_id", - variant: 0, - fullOn: false, - custom: false, - audienceMismatch: true, - }, - ], - }, - sdk, - context, - undefined - ); - - done(); - }); - }); - - it("should throw after finalized() call", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - publisher.publish.mockReturnValue(Promise.resolve()); - - context.variableValue("button.color", 17); - - expect(context.pending()).toEqual(1); - - context.finalize().then(() => { - expect(() => context.variableValue("button.color", 17)).toThrow(); - - done(); - }); - - expect(context.isFinalizing()).toEqual(true); - expect(() => context.variableValue("button.color", 17)).toThrow(); - }); - }); - - describe("peekVariableValue()", () => { - it("should not return variable values when unassigned", (done) => { - const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse); - - expect(context.pending()).toEqual(0); - - expect(context.peekVariableValue("banner.size", 17)).toEqual(17); - - done(); - }); - it("should return variable values when overridden", (done) => { - const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse); - - expect(context.pending()).toEqual(0); - - context.override("exp_test_ab", 0); - - expect(context.peekVariableValue("banner.size", 17)).toEqual("tiny"); - - done(); - }); - it("conflicting key disjoint audiences", (done) => { - const context1 = new Context(sdk, contextOptions, contextParams, disjointedContextResponse); - const context2 = new Context(sdk, contextOptions, contextParams, disjointedContextResponse); - - expect(context1.pending()).toEqual(0); - expect(context2.pending()).toEqual(0); - - expect(expectedVariants["exp_test_ab"]).not.toEqual(0); - expect(expectedVariants["exp_test_abc"]).not.toEqual(0); - - context1.attribute("age", 20); - expect(context1.peekVariableValue("icon", "square")).toEqual("arrow"); - - context2.attribute("age", 19); - expect(context2.peekVariableValue("icon", "square")).toEqual("circle"); - - expect(context1.pending()).toEqual(0); - expect(context2.pending()).toEqual(0); - - done(); - }); - - it("should pick lowest experiment id on conflicting key", (done) => { - const context = new Context(sdk, contextOptions, contextParams, lowestIdConflictingKeyContextResponse); - - expect(context.pending()).toEqual(0); - - expect(expectedVariants["exp_test_ab"]).not.toEqual(0); - expect(expectedVariants["exp_test_abc"]).not.toEqual(0); - - expect(context.peekVariableValue("icon", "square")).toEqual("circle"); - - done(); - }); - - it("should not queue exposures", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - const experiments = context.experiments(); - - for (const [key, experimentNames] of Object.entries(variableExperiments)) { - const experimentName = experimentNames[0]; - const actual = context.peekVariableValue(key, 17); - const eligible = experimentName !== "exp_test_not_eligible"; - - if (eligible && experiments.indexOf(experimentName) !== -1) { - expect(actual).toEqual(expectedVariables[key]); - } else { - expect(actual).toBe(17); - } - } - - expect(context.pending()).toEqual(0); - - done(); - }); - - it("should return defaultValue on unknown override variant", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - for (const experiment of getContextResponse.experiments) { - context.override(experiment.name, expectedVariants[experiment.name] + 11); - } - context.override("not_found", 3); - - for (const key of Object.keys(variableExperiments)) { - const actual = context.peekVariableValue(key, 17); - expect(actual).toBe(17); - } - - expect(context.pending()).toEqual(0); - - done(); - }); - - it("should return assigned variant on audience mismatch in non-strict mode", (done) => { - const context = new Context(sdk, contextOptions, contextParams, audienceContextResponse); - - expect(context.peekVariableValue("banner.size", "small")).toEqual("large"); - - done(); - }); - - it("should return control variant on audience mismatch in strict mode", (done) => { - const context = new Context(sdk, contextOptions, contextParams, audienceStrictContextResponse); - - expect(context.peekVariableValue("banner.size", "small")).toEqual("small"); - - done(); - }); - }); - - describe("variableKeys()", () => { - it("should return all active keys", (done) => { - const context = new Context(sdk, contextOptions, contextParams, refreshContextResponse); - expect(context.pending()).toEqual(0); - - expect(context.variableKeys()).toMatchObject(variableExperiments); - - expect(context.pending()).toEqual(0); - - done(); - }); - }); - - describe("track()", () => { - it("should queue goals", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - context.track("goal1", { amount: 125, hours: 245 }); - context.track("goal2", { tries: 7 }); - - expect(context.pending()).toEqual(2); - - context.track("goal2", { tests: 12 }); - - expect(context.pending()).toEqual(3); - - publisher.publish.mockReturnValue(Promise.resolve()); - - context.publish().then(() => { - expect(publisher.publish).toHaveBeenCalledWith( - { - publishedAt: 1611141535729, - units: publishUnits, - hashed: true, - sdkVersion: SDK_VERSION, - goals: [ - { - achievedAt: 1611141535729, - name: "goal1", - properties: { amount: 125, hours: 245 }, - }, - { - achievedAt: 1611141535729, - name: "goal2", - properties: { tries: 7 }, - }, - { - achievedAt: 1611141535729, - name: "goal2", - properties: { tests: 12 }, - }, - ], - }, - sdk, - context, - undefined - ); - - done(); - }); - }); - - it("should call event logger", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - - SDK.defaultEventLogger.mockClear(); - context.track("goal1", { amount: 125, hours: 245 }); - expect(SDK.defaultEventLogger).toHaveBeenCalledTimes(1); - expect(SDK.defaultEventLogger).toHaveBeenCalledWith(context, "goal", { - achievedAt: timeOrigin, - name: "goal1", - properties: { amount: 125, hours: 245 }, - }); - - done(); - }); - - it("should not throw when goal property values are numbers or objects with number values", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - expect(() => context.track("goal1", { test: { flt: 1.5, int: 2 } })).not.toThrowError(); - expect(() => context.track("goal1", { test: {} })).not.toThrowError(); - expect(() => context.track("goal1", { test: null })).not.toThrowError(); - - expect(context.pending()).toEqual(3); - - done(); - }); - - it("should not throw when goal properties is null or undefined", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - expect(() => context.track("goal1")).not.toThrowError(); - expect(() => context.track("goal1", null)).not.toThrowError(); - expect(() => context.track("goal1", undefined)).not.toThrowError(); - - expect(context.pending()).toEqual(3); - - done(); - }); - - it("should throw when goal properties not object", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - expect(() => context.track("goal1", 125.0)).toThrowError("Goal 'goal1' properties must be of type object."); - expect(() => context.track("goal1", true)).toThrowError("Goal 'goal1' properties must be of type object."); - expect(() => context.track("goal1", "testy")).toThrowError( - "Goal 'goal1' properties must be of type object." - ); - expect(() => context.track("goal1", [])).toThrowError("Goal 'goal1' properties must be of type object."); - - done(); - }); - - it("should throw after finalized() call", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - publisher.publish.mockReturnValue(Promise.resolve()); - - context.treatment("exp_test_ab"); - - expect(context.pending()).toEqual(1); - - context.finalize().then(() => { - expect(() => context.track("payment", { amount: 125 })).toThrow(); - - done(); - }); - - expect(context.isFinalizing()).toEqual(true); - expect(() => context.track("payment", { amount: 125 })).toThrow(); - }); - - it("should queue when not ready", (done) => { - const context = new Context(sdk, contextOptions, contextParams, Promise.resolve(getContextResponse)); - expect(context.pending()).toEqual(0); - expect(context.isReady()).toEqual(false); - - context.track("goal1", { amount: 125 }); - - expect(context.pending()).toEqual(1); - expect(context.isReady()).toEqual(false); - - context.ready().then(() => { - expect(context.pending()).toEqual(1); - - done(); - }); - }); - - it("should start timeout after ready if queue is not empty", (done) => { - jest.useFakeTimers("legacy"); - jest.spyOn(global, "setTimeout"); - - const publishDelay = 100; - const context = new Context( - sdk, - Object.assign(contextOptions, { publishDelay }), - contextParams, - Promise.resolve(getContextResponse) - ); - - expect(context.isReady()).toEqual(false); - expect(context.isFailed()).toEqual(false); - - jest.spyOn(Date, "now").mockImplementation(() => timeOrigin); - - context.track("goal1", { amount: 125 }); - - expect(context.pending()).toEqual(1); - - context.ready().then(() => { - expect(setTimeout).toHaveBeenCalledTimes(1); - expect(setTimeout).toHaveBeenLastCalledWith(expect.anything(), publishDelay); - - jest.advanceTimersByTime(publishDelay - 1); - - expect(publisher.publish).not.toHaveBeenCalled(); - - publisher.publish.mockReturnValue(Promise.resolve({})); - - jest.spyOn(Date, "now").mockImplementation(() => timeOrigin + 100); - - jest.advanceTimersByTime(2); - - expect(publisher.publish).toHaveBeenCalledTimes(1); - expect(publisher.publish).toHaveBeenCalledWith( - { - publishedAt: 1611141535829, - units: publishUnits, - hashed: true, - sdkVersion: SDK_VERSION, - goals: [ - { - name: "goal1", - achievedAt: 1611141535729, - properties: { amount: 125 }, - }, - ], - }, - sdk, - context, - undefined - ); - - done(); - }); - }); - }); - - describe("publish()", () => { - it("should not call client publish when queue is empty", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - publisher.publish.mockReturnValue(Promise.resolve()); - - context.publish().then(() => { - expect(publisher.publish).not.toHaveBeenCalled(); - done(); - }); - }); - - it("should propagate client error message", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - context.track("goal1", { amount: 125 }); - - publisher.publish.mockReturnValue(Promise.reject("test")); - - context.publish().catch((e) => { - expect(e).toEqual("test"); - - done(); - }); - }); - - it("should call client publish", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - - context.treatment("exp_test_ab"); - context.treatment("exp_test_not_eligible"); - - Date.now.mockImplementation(() => timeOrigin + 1); // ensure that time is kept separately per event - context.track("goal1", { amount: 125, hours: 245 }); - - Date.now.mockImplementation(() => timeOrigin + 2); - context.attribute("attr1", "value1"); - - Date.now.mockImplementation(() => timeOrigin + 3); - context.attributes({ - attr2: "value2", - attr3: 3, - attr4: 5.0, - attr5: true, - attr6: [1, 2, 3, 4], - attr7: null, - attr8: [], - attr9: [null, 1, 2], - attr10: ["one", null, "two"], - attr11: [null, null], - }); - - expect(context.pending()).toEqual(3); - - publisher.publish.mockReturnValue(Promise.resolve()); - - jest.spyOn(Date, "now").mockImplementation(() => timeOrigin + 100); - - context.publish().then(() => { - expect(publisher.publish).toHaveBeenCalledTimes(1); - expect(publisher.publish).toHaveBeenCalledWith( - { - publishedAt: 1611141535829, - units: publishUnits, - hashed: true, - sdkVersion: SDK_VERSION, - exposures: [ - { - id: 1, - name: "exp_test_ab", - unit: "session_id", - exposedAt: 1611141535729, - variant: 1, - assigned: true, - eligible: true, - overridden: false, - fullOn: false, - custom: false, - audienceMismatch: false, - }, - { - id: 3, - name: "exp_test_not_eligible", - unit: "user_id", - exposedAt: 1611141535729, - variant: 0, - assigned: true, - eligible: false, - overridden: false, - fullOn: false, - custom: false, - audienceMismatch: false, - }, - ], - goals: [ - { - name: "goal1", - achievedAt: 1611141535730, - properties: { amount: 125, hours: 245 }, - }, - ], - attributes: [ - { - name: "attr1", - setAt: 1611141535731, - value: "value1", - }, - { - name: "attr2", - setAt: 1611141535732, - value: "value2", - }, - { - name: "attr3", - setAt: 1611141535732, - value: 3, - }, - { - name: "attr4", - setAt: 1611141535732, - value: 5.0, - }, - { - name: "attr5", - setAt: 1611141535732, - value: true, - }, - { - name: "attr6", - setAt: 1611141535732, - value: [1, 2, 3, 4], - }, - { - name: "attr7", - setAt: 1611141535732, - value: null, - }, - { - name: "attr8", - setAt: 1611141535732, - value: [], - }, - { - name: "attr9", - setAt: 1611141535732, - value: [null, 1, 2], - }, - { - name: "attr10", - setAt: 1611141535732, - value: ["one", null, "two"], - }, - - { - name: "attr11", - setAt: 1611141535732, - value: [null, null], - }, - ], - }, - sdk, - context, - undefined - ); - - expect(context.pending()).toEqual(0); - - done(); - }); - }); - - it("should pass through request options", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - - context.track("goal1", { amount: 125, hours: 245 }); - - publisher.publish.mockReturnValue(Promise.resolve()); - - jest.spyOn(Date, "now").mockImplementation(() => timeOrigin + 100); - - context.publish({ timeout: 1234 }).then(() => { - expect(publisher.publish).toHaveBeenCalledTimes(1); - expect(publisher.publish).toHaveBeenCalledWith( - { - publishedAt: 1611141535829, - units: publishUnits, - hashed: true, - sdkVersion: SDK_VERSION, - goals: [ - { - name: "goal1", - achievedAt: 1611141535729, - properties: { amount: 125, hours: 245 }, - }, - ], - }, - sdk, - context, - { - timeout: 1234, - } - ); - - expect(context.pending()).toEqual(0); - - done(); - }); - }); - - it("should call event logger on error", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - - context.track("goal1", { amount: 125, hours: 245 }); - - publisher.publish.mockReturnValue(Promise.reject("test error")); - - SDK.defaultEventLogger.mockClear(); - context.publish().catch((error) => { - expect(SDK.defaultEventLogger).toHaveBeenCalledTimes(1); - expect(SDK.defaultEventLogger).toHaveBeenCalledWith(context, "error", error); - - done(); - }); - }); - - it("should call event logger on success", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - - context.track("goal1", { amount: 125, hours: 245 }); - - publisher.publish.mockReturnValue(Promise.resolve()); - - jest.spyOn(Date, "now").mockImplementation(() => timeOrigin + 100); - - SDK.defaultEventLogger.mockClear(); - context.publish().then(() => { - expect(SDK.defaultEventLogger).toHaveBeenCalledTimes(1); - expect(SDK.defaultEventLogger).toHaveBeenCalledWith(context, "publish", { - publishedAt: 1611141535829, - units: publishUnits, - hashed: true, - sdkVersion: SDK_VERSION, - goals: [ - { - achievedAt: 1611141535729, - name: "goal1", - properties: { amount: 125, hours: 245 }, - }, - ], - }); - - done(); - }); - }); - - it("should not call client publish when failed", (done) => { - const context = new Context(sdk, contextOptions, contextParams, Promise.reject("bad request error text")); - context.ready().then(() => { - context.treatment("exp_test_ab"); - - Date.now.mockImplementation(() => timeOrigin + 1); // ensure that time is kept separately per event - context.track("goal1", { amount: 125, hours: 245 }); - - expect(context.pending()).toEqual(2); - - context.publish().then(() => { - expect(publisher.publish).not.toHaveBeenCalled(); - expect(context.pending()).toEqual(0); - - done(); - }); - }); - }); - - it("should reset internal queues and keep attributes overrides and custom assignments", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - - context.treatment("exp_test_ab"); - context.track("goal1", { amount: 125, hours: 245 }); - context.attribute("attr1", "value1"); - - context.override("not_found", 3); - expect(context.treatment("not_found")).toEqual(3); - - context.customAssignment("exp_test_abc", 3); - expect(context.treatment("exp_test_abc")).toEqual(3); - - expect(context.pending()).toEqual(4); - - publisher.publish.mockReturnValue(Promise.resolve({})); - - jest.spyOn(Date, "now").mockImplementation(() => timeOrigin + 100); - - context - .publish() - .then(() => { - expect(publisher.publish).toHaveBeenCalledTimes(1); - expect(publisher.publish).toHaveBeenCalledWith( - { - publishedAt: 1611141535829, - units: publishUnits, - hashed: true, - sdkVersion: SDK_VERSION, - exposures: [ - { - id: 1, - name: "exp_test_ab", - unit: "session_id", - exposedAt: 1611141535729, - variant: 1, - assigned: true, - eligible: true, - overridden: false, - fullOn: false, - custom: false, - audienceMismatch: false, - }, - { - id: 0, - name: "not_found", - unit: null, - exposedAt: 1611141535729, - variant: 3, - assigned: false, - eligible: true, - overridden: true, - fullOn: false, - custom: false, - audienceMismatch: false, - }, - { - id: 2, - name: "exp_test_abc", - unit: "session_id", - exposedAt: 1611141535729, - variant: 3, - assigned: true, - eligible: true, - overridden: false, - fullOn: false, - custom: true, - audienceMismatch: false, - }, - ], - goals: [ - { - name: "goal1", - achievedAt: 1611141535729, - properties: { amount: 125, hours: 245 }, - }, - ], - attributes: [ - { - name: "attr1", - setAt: 1611141535729, - value: "value1", - }, - ], - }, - sdk, - context, - undefined - ); - - expect(context.pending()).toEqual(0); - - publisher.publish.mockClear(); - }) - .then(() => { - context.track("goal2", { test: 999 }); - - return context.publish(); - }) - .then(() => { - expect(publisher.publish).toHaveBeenCalledTimes(1); - expect(publisher.publish).toHaveBeenCalledWith( - { - publishedAt: 1611141535829, - units: publishUnits, - hashed: true, - sdkVersion: SDK_VERSION, - goals: [ - { - name: "goal2", - achievedAt: 1611141535829, - properties: { test: 999 }, - }, - ], - attributes: [ - { - name: "attr1", - setAt: 1611141535729, - value: "value1", - }, - ], - }, - sdk, - context, - undefined - ); - - expect(context.pending()).toEqual(0); - - expect(context.treatment("exp_test_abc")).toEqual(3); - expect(context.treatment("not_found")).toEqual(3); - - expect(context.pending()).toEqual(0); - - done(); - }); - }); - - it("should be called options.publishDelay ms after an exposure being queued", () => { - jest.useFakeTimers("legacy"); - jest.spyOn(global, "setTimeout"); - - const publishDelay = 100; - const context = new Context( - sdk, - Object.assign(contextOptions, { publishDelay }), - contextParams, - getContextResponse - ); - - expect(context.isReady()).toEqual(true); - expect(context.isFailed()).toEqual(false); - - context.treatment("exp_test_ab"); - - expect(context.pending()).toEqual(1); - expect(setTimeout).toHaveBeenCalledTimes(1); - expect(setTimeout).toHaveBeenLastCalledWith(expect.anything(), publishDelay); - - context.track("goal1", { amount: 125 }); - - expect(context.pending()).toEqual(2); - expect(setTimeout).toHaveBeenCalledTimes(1); // no new calls - expect(setTimeout).toHaveBeenLastCalledWith(expect.anything(), publishDelay); - - jest.advanceTimersByTime(publishDelay - 1); - - expect(publisher.publish).not.toHaveBeenCalled(); - - publisher.publish.mockReturnValue(Promise.resolve({})); - - jest.advanceTimersByTime(2); - - expect(publisher.publish).toHaveBeenCalledTimes(1); - }); - - it("should be called options.publishDelay ms after a goal being queued", () => { - jest.useFakeTimers("legacy"); - jest.spyOn(global, "setTimeout"); - - const publishDelay = 100; - const context = new Context( - sdk, - Object.assign(contextOptions, { publishDelay }), - contextParams, - getContextResponse - ); - - expect(context.isReady()).toEqual(true); - expect(context.isFailed()).toEqual(false); - - context.track("goal1", { amount: 125 }); - - expect(context.pending()).toEqual(1); - expect(setTimeout).toHaveBeenCalledTimes(1); - expect(setTimeout).toHaveBeenLastCalledWith(expect.anything(), publishDelay); - - context.treatment("exp_test_ab"); - - expect(context.pending()).toEqual(2); - expect(setTimeout).toHaveBeenCalledTimes(1); // no new calls - expect(setTimeout).toHaveBeenLastCalledWith(expect.anything(), publishDelay); - - jest.advanceTimersByTime(publishDelay - 1); - - expect(publisher.publish).not.toHaveBeenCalled(); - - publisher.publish.mockReturnValue(Promise.resolve({})); - - jest.advanceTimersByTime(2); - - expect(publisher.publish).toHaveBeenCalledTimes(1); - }); - - it("should throw after finalized() call", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - publisher.publish.mockReturnValue(Promise.resolve()); - - context.treatment("exp_test_ab"); - - expect(context.pending()).toEqual(1); - - context.finalize().then(() => { - expect(() => context.publish()).toThrow(); - - done(); - }); - - expect(context.isFinalizing()).toEqual(true); - expect(() => context.publish()).toThrow(); - }); - }); - - describe("finalize()", () => { - it("should not call client publish when queue is empty", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - publisher.publish.mockReturnValue(Promise.resolve()); - - context.finalize().then(() => { - expect(publisher.publish).not.toHaveBeenCalled(); - expect(context.isFinalizing()).toEqual(false); - expect(context.isFinalized()).toEqual(true); - done(); - }); - - expect(context.isFinalizing()).toEqual(false); - expect(context.isFinalized()).toEqual(true); - }); - - it("should propagate client error message", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - context.treatment("exp_test_ab"); - - publisher.publish.mockReturnValue(Promise.reject("test")); - - context.finalize().catch((e) => { - expect(e).toEqual("test"); - expect(context.isFinalizing()).toEqual(false); - expect(context.isFinalized()).toEqual(false); - - done(); - }); - - expect(context.isFinalizing()).toEqual(true); - expect(context.isFinalized()).toEqual(false); - }); - - it("should call client publish", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - - context.treatment("exp_test_ab"); - - expect(context.pending()).toEqual(1); - - publisher.publish.mockReturnValue(Promise.resolve()); - - jest.spyOn(Date, "now").mockImplementation(() => timeOrigin + 100); - - context.finalize().then(() => { - expect(publisher.publish).toHaveBeenCalledTimes(1); - expect(publisher.publish).toHaveBeenCalledWith( - { - publishedAt: 1611141535829, - units: publishUnits, - hashed: true, - sdkVersion: SDK_VERSION, - exposures: [ - { - id: 1, - name: "exp_test_ab", - unit: "session_id", - exposedAt: 1611141535729, - variant: 1, - assigned: true, - eligible: true, - overridden: false, - fullOn: false, - custom: false, - audienceMismatch: false, - }, - ], - }, - sdk, - context, - undefined - ); - - expect(context.pending()).toEqual(0); - expect(context.isFinalizing()).toEqual(false); - expect(context.isFinalized()).toEqual(true); - - done(); - }); - - expect(context.isFinalizing()).toEqual(true); - expect(context.isFinalized()).toEqual(false); - }); - - it("should pass through request options", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - - context.treatment("exp_test_ab"); - - expect(context.pending()).toEqual(1); - - publisher.publish.mockReturnValue(Promise.resolve()); - - jest.spyOn(Date, "now").mockImplementation(() => timeOrigin + 100); - - context.finalize({ timeout: 1234 }).then(() => { - expect(publisher.publish).toHaveBeenCalledTimes(1); - expect(publisher.publish).toHaveBeenCalledWith( - { - publishedAt: 1611141535829, - units: publishUnits, - hashed: true, - sdkVersion: SDK_VERSION, - exposures: [ - { - id: 1, - name: "exp_test_ab", - unit: "session_id", - exposedAt: 1611141535729, - variant: 1, - assigned: true, - eligible: true, - overridden: false, - fullOn: false, - custom: false, - audienceMismatch: false, - }, - ], - }, - sdk, - context, - { timeout: 1234 } - ); - - expect(context.pending()).toEqual(0); - expect(context.isFinalizing()).toEqual(false); - expect(context.isFinalized()).toEqual(true); - - done(); - }); - - expect(context.isFinalizing()).toEqual(true); - expect(context.isFinalized()).toEqual(false); - }); - - it("should call event logger on error", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - - context.treatment("exp_test_ab"); - - publisher.publish.mockReturnValue(Promise.reject("test error")); - - SDK.defaultEventLogger.mockClear(); - context.finalize().catch((error) => { - expect(SDK.defaultEventLogger).toHaveBeenCalledTimes(1); - expect(SDK.defaultEventLogger).toHaveBeenCalledWith(context, "error", error); - expect(context.isFinalizing()).toEqual(false); - expect(context.isFinalized()).toEqual(false); - - done(); - }); - - expect(context.isFinalizing()).toEqual(true); - expect(context.isFinalized()).toEqual(false); - }); - - it("should call event logger on success", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - - context.treatment("exp_test_ab"); - - publisher.publish.mockReturnValue(Promise.resolve()); - - SDK.defaultEventLogger.mockClear(); - context.finalize().then(() => { - expect(SDK.defaultEventLogger).toHaveBeenCalledTimes(2); - expect(SDK.defaultEventLogger).toHaveBeenLastCalledWith(context, "finalize", undefined); - expect(context.isFinalizing()).toEqual(false); - expect(context.isFinalized()).toEqual(true); - - done(); - }); - - expect(context.isFinalizing()).toEqual(true); - expect(context.isFinalized()).toEqual(false); - }); - - it("should not call client publish when failed", (done) => { - const context = new Context(sdk, contextOptions, contextParams, Promise.reject("bad request error text")); - context.ready().then(() => { - context.treatment("exp_test_ab"); - - expect(context.pending()).toEqual(1); - - context.finalize().then(() => { - expect(publisher.publish).not.toHaveBeenCalled(); - expect(context.pending()).toEqual(0); - expect(context.isFinalizing()).toEqual(false); - expect(context.isFinalized()).toEqual(true); - - done(); - }); - - expect(context.isFinalizing()).toEqual(false); - expect(context.isFinalized()).toEqual(true); - }); - }); - - it("should return current promise when called twice", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - - context.treatment("exp_test_ab"); - - publisher.publish.mockReturnValue(Promise.resolve()); - - const firstPromise = context.finalize(); - const secondPromise = context.finalize(); - - expect(secondPromise).toBe(firstPromise); - - secondPromise.then(() => { - expect(context.isFinalizing()).toEqual(false); - expect(context.isFinalized()).toEqual(true); - - done(); - }); - - expect(context.isFinalizing()).toEqual(true); - expect(context.isFinalized()).toEqual(false); - }); - - it("should return completed promise when already finalized", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - - context.treatment("exp_test_ab"); - - publisher.publish.mockReturnValue(Promise.resolve()); - - context.finalize().then(() => { - expect(context.isFinalizing()).toEqual(false); - expect(context.isFinalized()).toEqual(true); - - context.finalize().then(() => { - done(); - }); - }); - }); - - it("should cancel refresh timer", (done) => { - jest.useFakeTimers("legacy"); - jest.spyOn(global, "setInterval"); - jest.spyOn(global, "clearInterval"); - - const refreshPeriod = 1000; - const context = new Context( - sdk, - Object.assign(contextOptions, { refreshPeriod }), - contextParams, - getContextResponse - ); - - expect(context.isReady()).toEqual(true); - expect(context.isFailed()).toEqual(false); - - expect(setInterval).toHaveBeenCalledTimes(1); - expect(setInterval).toHaveBeenCalledWith(expect.anything(), refreshPeriod); - const timerId = setInterval.mock.results[0].value; - - context.finalize().then(() => { - expect(context.isFinalizing()).toEqual(false); - expect(context.isFinalized()).toEqual(true); - - expect(clearInterval).toHaveBeenCalledTimes(1); - expect(clearInterval).toHaveBeenCalledWith(timerId); - - done(); - }); - }); - }); - - describe("override()", () => { - it("should be callable before ready()", (done) => { - const context = new Context(sdk, contextOptions, contextParams, Promise.resolve(getContextResponse)); - expect(context.isReady()).toEqual(false); - expect(context.isFailed()).toEqual(false); - expect(context.isFinalized()).toEqual(false); - - context.override("exp_test_ab", 1); - context.overrides({ - exp_test_ab: 2, - exp_test_abc: 2, - not_found: 3, - }); - - context.ready().then(() => { - expect(context.isReady()).toEqual(true); - expect(context.data()).toStrictEqual(getContextResponse); - - context.treatment("exp_test_ab"); - context.treatment("exp_test_abc"); - - publisher.publish.mockReturnValue(Promise.resolve()); - - jest.spyOn(Date, "now").mockImplementation(() => timeOrigin + 100); - - context.publish().then(() => { - expect(publisher.publish).toHaveBeenCalledTimes(1); - expect(publisher.publish).toHaveBeenCalledWith( - { - publishedAt: 1611141535829, - units: publishUnits, - hashed: true, - sdkVersion: SDK_VERSION, - exposures: [ - { - id: 1, - name: "exp_test_ab", - unit: "session_id", - exposedAt: 1611141535729, - variant: 2, - assigned: false, - eligible: true, - overridden: true, - fullOn: false, - custom: false, - audienceMismatch: false, - }, - { - id: 2, - name: "exp_test_abc", - unit: "session_id", - exposedAt: 1611141535729, - variant: 2, - assigned: false, - eligible: true, - overridden: true, - fullOn: false, - custom: false, - audienceMismatch: false, - }, - ], - }, - sdk, - context, - undefined - ); - - done(); - }); - }); - }); - }); - - describe("customAssignment()", () => { - it("should override natural assignment and set custom flag", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - context.customAssignment("exp_test_abc", 11); - - expect(context.pending()).toEqual(0); // should not queue exposures - - context.treatment("exp_test_abc"); - - expect(context.pending()).toEqual(1); - - publisher.publish.mockReturnValue(Promise.resolve()); - - context.publish().then(() => { - expect(publisher.publish).toHaveBeenCalledWith( - { - publishedAt: 1611141535729, - units: publishUnits, - hashed: true, - sdkVersion: SDK_VERSION, - exposures: [ - { - id: 2, - assigned: true, - eligible: true, - exposedAt: 1611141535729, - name: "exp_test_abc", - overridden: false, - unit: "session_id", - variant: 11, - fullOn: false, - custom: true, - audienceMismatch: false, - }, - ], - }, - sdk, - context, - undefined - ); - - done(); - }); - }); - - it("should not override full-on or non-eligible assignment", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - context.customAssignment("exp_test_not_eligible", 11); - context.customAssignment("exp_test_fullon", 11); - - expect(context.pending()).toEqual(0); // should not queue exposures - - context.treatment("exp_test_not_eligible"); - context.treatment("exp_test_fullon"); - - expect(context.pending()).toEqual(2); - - publisher.publish.mockReturnValue(Promise.resolve()); - - context.publish().then(() => { - expect(publisher.publish).toHaveBeenCalledWith( - { - publishedAt: 1611141535729, - units: publishUnits, - hashed: true, - sdkVersion: SDK_VERSION, - exposures: [ - { - id: 3, - assigned: true, - eligible: false, - exposedAt: 1611141535729, - name: "exp_test_not_eligible", - overridden: false, - unit: "user_id", - variant: 0, - fullOn: false, - custom: false, - audienceMismatch: false, - }, - { - id: 4, - assigned: true, - eligible: true, - exposedAt: 1611141535729, - name: "exp_test_fullon", - overridden: false, - unit: "session_id", - variant: 2, - fullOn: true, - custom: false, - audienceMismatch: false, - }, - ], - }, - sdk, - context, - undefined - ); - - done(); - }); - }); - - it("should be callable before ready()", (done) => { - const context = new Context(sdk, contextOptions, contextParams, Promise.resolve(getContextResponse)); - expect(context.isReady()).toEqual(false); - expect(context.isFailed()).toEqual(false); - expect(context.isFinalized()).toEqual(false); - - context.customAssignment("exp_test_ab", 1); - context.customAssignments({ - exp_test_ab: 2, - exp_test_abc: 2, - not_found: 3, - }); - - context.ready().then(() => { - expect(context.isReady()).toEqual(true); - expect(context.data()).toStrictEqual(getContextResponse); - - context.treatment("exp_test_ab"); - context.treatment("exp_test_abc"); - - publisher.publish.mockReturnValue(Promise.resolve()); - - jest.spyOn(Date, "now").mockImplementation(() => timeOrigin + 100); - - context.publish().then(() => { - expect(publisher.publish).toHaveBeenCalledTimes(1); - expect(publisher.publish).toHaveBeenCalledWith( - { - publishedAt: 1611141535829, - units: publishUnits, - hashed: true, - sdkVersion: SDK_VERSION, - exposures: [ - { - id: 1, - name: "exp_test_ab", - unit: "session_id", - exposedAt: 1611141535729, - variant: 2, - assigned: true, - eligible: true, - overridden: false, - fullOn: false, - custom: true, - audienceMismatch: false, - }, - { - id: 2, - name: "exp_test_abc", - unit: "session_id", - exposedAt: 1611141535729, - variant: 2, - assigned: true, - eligible: true, - overridden: false, - fullOn: false, - custom: true, - audienceMismatch: false, - }, - ], - }, - sdk, - context, - undefined - ); - - done(); - }); - }); - }); - - it("should throw after finalized() call", (done) => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - publisher.publish.mockReturnValue(Promise.resolve()); - - context.treatment("exp_test_ab"); - - expect(context.pending()).toEqual(1); - - context.finalize().then(() => { - expect(() => context.customAssignment("exp_test_ab", 3)).toThrow(); // finalizing - - done(); - }); - - expect(context.isFinalizing()).toEqual(true); - expect(() => context.customAssignment("exp_test_ab", 3)).toThrow(); // finalizing - }); - }); - describe("customFieldKeys()", () => { - it("should return custom field keys", () => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - const keys = context.customFieldKeys(); - - expect(context.isReady()).toEqual(true); - expect(keys).toEqual([ - "country", - "json_object", - "json_array", - "json_number", - "json_string", - "json_boolean", - "json_null", - "json_invalid", - "languages", - "text_field", - "string_field", - "number_field", - "boolean_field", - "false_boolean_field", - "invalid_type_field", - ]); - }); - }); - - describe("customFieldValue()", () => { - it("should return custom field value", () => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - const value = context.customFieldValue("exp_test_custom_fields", "country"); - - expect(context.isReady()).toEqual(true); - expect(value).toEqual("US,PT,ES"); - }); - - it("should return parsed JSON fields", () => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - expect(context.customFieldValue("exp_test_abc", "json_object")).toEqual({ 123: 1, 456: 0 }); - expect(context.customFieldValue("exp_test_abc", "json_array")).toEqual(["hello", "world"]); - expect(context.customFieldValue("exp_test_abc", "json_number")).toEqual(123); - expect(context.customFieldValue("exp_test_abc", "json_string")).toEqual("hello"); - expect(context.customFieldValue("exp_test_abc", "json_boolean")).toEqual(true); - expect(context.customFieldValue("exp_test_abc", "json_null")).toEqual(null); - }); - - it("should return string and text fields", () => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - expect(context.customFieldValue("exp_test_custom_fields", "text_field")).toEqual("hello text"); - expect(context.customFieldValue("exp_test_custom_fields", "string_field")).toEqual("hello string"); - }); - - it("should return parsed number fields", () => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - expect(context.customFieldValue("exp_test_custom_fields", "number_field")).toEqual(123); - }); - - it("should return parsed boolean fields", () => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - expect(context.customFieldValue("exp_test_custom_fields", "boolean_field")).toEqual(true); - expect(context.customFieldValue("exp_test_custom_fields", "false_boolean_field")).toEqual(false); - }); - - it("should console an error when JSON cannot be parsed", () => { - const errorSpy = jest.spyOn(console, "error"); - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - expect(context.customFieldValue("exp_test_abc", "json_invalid")).toEqual(null); - expect(errorSpy).toHaveBeenCalledTimes(1); - expect(errorSpy).toHaveBeenCalledWith( - "Failed to parse JSON custom field value 'json_invalid' for experiment 'exp_test_abc'" - ); - }); - - it("should console an error when a field type is invalid", () => { - const errorSpy = jest.spyOn(console, "error"); - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - - expect(context.customFieldValue("exp_test_custom_fields", "invalid_type_field")).toEqual(null); - expect(errorSpy).toHaveBeenCalledTimes(1); - expect(errorSpy).toHaveBeenCalledWith( - "Unknown custom field type 'invalid' for experiment 'exp_test_custom_fields' and key 'invalid_type_field' - you may need to upgrade to the latest SDK version" - ); - }); - }); - - describe("customFieldValueType()", () => { - it("should return custom field value type", () => { - const context = new Context(sdk, contextOptions, contextParams, getContextResponse); - expect(context.pending()).toEqual(0); - const value = context.customFieldValueType("exp_test_custom_fields", "country"); - - expect(context.isReady()).toEqual(true); - expect(value).toEqual("string"); - }); - }); - - describe("includeSystemAttributes", () => { - it("should not include system attributes by default", (done) => { - const defaultOptions = { - publishDelay: -1, - refreshPeriod: 0, - }; - - const context = new Context(sdk, defaultOptions, contextParams, getContextResponse); - publisher.publish.mockReturnValue(Promise.resolve()); - - context.treatment("exp_test_ab"); - - context.publish().then(() => { - const call = publisher.publish.mock.calls[0]; - const request = call[0]; - - expect(request.attributes).toBeUndefined(); - - done(); - }); - }); - - it("should include system attributes when includeSystemAttributes is true", (done) => { - const optionsWithSystemAttrs = { - publishDelay: -1, - refreshPeriod: 0, - includeSystemAttributes: true, - }; - - const context = new Context(sdk, optionsWithSystemAttrs, contextParams, getContextResponse); - publisher.publish.mockReturnValue(Promise.resolve()); - - context.treatment("exp_test_ab"); - - context.publish().then(() => { - const call = publisher.publish.mock.calls[0]; - const request = call[0]; - - expect(request.attributes).toBeDefined(); - expect(request.attributes.length).toBeGreaterThanOrEqual(4); - - const sdkNameAttr = request.attributes.find((a) => a.name === "sdk_name"); - const sdkVersionAttr = request.attributes.find((a) => a.name === "sdk_version"); - const applicationAttr = request.attributes.find((a) => a.name === "application"); - const environmentAttr = request.attributes.find((a) => a.name === "environment"); - - expect(sdkNameAttr).toBeDefined(); - expect(sdkNameAttr.value).toEqual("absmartly-javascript-sdk"); - expect(sdkNameAttr.setAt).toEqual(expect.any(Number)); - - expect(sdkVersionAttr).toBeDefined(); - expect(sdkVersionAttr.value).toEqual(SDK_VERSION); - expect(sdkVersionAttr.setAt).toEqual(expect.any(Number)); - - expect(applicationAttr).toBeDefined(); - expect(applicationAttr.value).toEqual("website"); - expect(applicationAttr.setAt).toEqual(expect.any(Number)); - - expect(environmentAttr).toBeDefined(); - expect(environmentAttr.value).toEqual("production"); - expect(environmentAttr.setAt).toEqual(expect.any(Number)); - - done(); - }); - }); - - it("should prepend system attributes before user attributes", (done) => { - const optionsWithSystemAttrs = { - publishDelay: -1, - refreshPeriod: 0, - includeSystemAttributes: true, - }; - - const context = new Context(sdk, optionsWithSystemAttrs, contextParams, getContextResponse); - publisher.publish.mockReturnValue(Promise.resolve()); - - context.attribute("custom_attr", "custom_value"); - context.treatment("exp_test_ab"); - - context.publish().then(() => { - const call = publisher.publish.mock.calls[0]; - const request = call[0]; - - expect(request.attributes[0].name).toEqual("sdk_name"); - expect(request.attributes[1].name).toEqual("sdk_version"); - expect(request.attributes[2].name).toEqual("application"); - expect(request.attributes[3].name).toEqual("environment"); - expect(request.attributes[4].name).toEqual("custom_attr"); - expect(request.attributes[4].value).toEqual("custom_value"); - - done(); - }); - }); - - it("should include app_version when application version is set", (done) => { - client.getApplication.mockReturnValueOnce({ name: "website", version: 3 }); - - const optionsWithSystemAttrs = { - publishDelay: -1, - refreshPeriod: 0, - includeSystemAttributes: true, - }; - - const context = new Context(sdk, optionsWithSystemAttrs, contextParams, getContextResponse); - publisher.publish.mockReturnValue(Promise.resolve()); - - context.treatment("exp_test_ab"); - - context.publish().then(() => { - const call = publisher.publish.mock.calls[0]; - const request = call[0]; - - const appVersionAttr = request.attributes.find((a) => a.name === "app_version"); - expect(appVersionAttr).toBeDefined(); - expect(appVersionAttr.value).toEqual(3); - - done(); - }); - }); - - it("should not include app_version when application version is 0", (done) => { - const optionsWithSystemAttrs = { - publishDelay: -1, - refreshPeriod: 0, - includeSystemAttributes: true, - }; - - const context = new Context(sdk, optionsWithSystemAttrs, contextParams, getContextResponse); - publisher.publish.mockReturnValue(Promise.resolve()); - - context.treatment("exp_test_ab"); - - context.publish().then(() => { - const call = publisher.publish.mock.calls[0]; - const request = call[0]; - - const appVersionAttr = request.attributes.find((a) => a.name === "app_version"); - expect(appVersionAttr).toBeUndefined(); - - done(); - }); - }); - - it("should include app_version when application version is a semver string", (done) => { - client.getApplication.mockReturnValueOnce({ name: "website", version: "1.2.3" }); - - const optionsWithSystemAttrs = { - publishDelay: -1, - refreshPeriod: 0, - includeSystemAttributes: true, - }; - - const context = new Context(sdk, optionsWithSystemAttrs, contextParams, getContextResponse); - publisher.publish.mockReturnValue(Promise.resolve()); - - context.treatment("exp_test_ab"); - - context.publish().then(() => { - const call = publisher.publish.mock.calls[0]; - const request = call[0]; - - const appVersionAttr = request.attributes.find((a) => a.name === "app_version"); - expect(appVersionAttr).toBeDefined(); - expect(appVersionAttr.value).toEqual("1.2.3"); - - done(); - }); - }); - - it("should include app_version with application as plain string", (done) => { - client.getApplication.mockReturnValueOnce({ name: "website", version: 0 }); - - const optionsWithSystemAttrs = { - publishDelay: -1, - refreshPeriod: 0, - includeSystemAttributes: true, - }; - - const context = new Context(sdk, optionsWithSystemAttrs, contextParams, getContextResponse); - publisher.publish.mockReturnValue(Promise.resolve()); - - context.treatment("exp_test_ab"); - - context.publish().then(() => { - const call = publisher.publish.mock.calls[0]; - const request = call[0]; - - const applicationAttr = request.attributes.find((a) => a.name === "application"); - expect(applicationAttr).toBeDefined(); - expect(applicationAttr.value).toEqual("website"); - - const appVersionAttr = request.attributes.find((a) => a.name === "app_version"); - expect(appVersionAttr).toBeUndefined(); - - done(); - }); - }); - - it("should only include user attributes when includeSystemAttributes is not set", (done) => { - const defaultOptions = { - publishDelay: -1, - refreshPeriod: 0, - }; - - const context = new Context(sdk, defaultOptions, contextParams, getContextResponse); - publisher.publish.mockReturnValue(Promise.resolve()); - - context.attribute("custom_attr", "custom_value"); - context.treatment("exp_test_ab"); - - context.publish().then(() => { - const call = publisher.publish.mock.calls[0]; - const request = call[0]; - - expect(request.attributes).toEqual([ - { name: "custom_attr", value: "custom_value", setAt: expect.any(Number) }, - ]); - - done(); - }); - }); - }); -}); diff --git a/src/__tests__/fetch-shim.test.js b/src/__tests__/fetch-shim.test.js deleted file mode 100644 index d214b7a..0000000 --- a/src/__tests__/fetch-shim.test.js +++ /dev/null @@ -1,135 +0,0 @@ -// eslint-disable-next-line no-shadow -import { fetch } from "../fetch-shim"; -// eslint-disable-next-line no-shadow -import { AbortController } from "../abort"; - -describe("fetch", () => { - it("should be a function", () => { - expect(fetch).toEqual(expect.any(Function)); - }); - - describe("fetch()", () => { - let xhr; - - beforeEach(() => { - xhr = { - setRequestHeader: jest.fn(), - getAllResponseHeaders: jest.fn().mockReturnValue("X-Foo: bar\nX-Foo:baz"), - open: jest.fn(), - send: jest.fn(), - status: 200, - statusText: "OK", - responseText: '{"a":"b"}', - responseURL: "/foo?redirect", - abort: jest.fn(), - }; - - global.XMLHttpRequest = jest.fn(() => xhr); - }); - - afterEach(() => { - delete global.XMLHttpRequest; - }); - - it("sanity test", async () => { - fetch("/foo", { headers: { a: "b" } }) - .then((response) => { - expect(response).toMatchObject({ - text: expect.any(Function), - json: expect.any(Function), - blob: expect.any(Function), - clone: expect.any(Function), - headers: expect.any(Object), - }); - expect(response.clone()).not.toBe(response); - expect(response.clone().url).toEqual("/foo?redirect"); - expect(response.headers.get).toEqual(expect.any(Function)); - expect(response.headers.get("x-foo")).toEqual("bar,baz"); - return response.json(); - }) - .then((data) => { - expect(data).toEqual({ a: "b" }); - - expect(xhr.setRequestHeader).toHaveBeenCalledTimes(1); - expect(xhr.setRequestHeader).toHaveBeenCalledWith("a", "b"); - expect(xhr.open).toHaveBeenCalledTimes(1); - expect(xhr.open).toHaveBeenCalledWith("get", "/foo", true); - expect(xhr.send).toHaveBeenCalledTimes(1); - expect(xhr.send).toHaveBeenCalledWith(null); - }); - - expect(xhr.onload).toEqual(expect.any(Function)); - expect(xhr.onerror).toEqual(expect.any(Function)); - expect(xhr.onabort).toEqual(expect.any(Function)); - - xhr.onload(); - }); - - it("handles empty header values", async () => { - xhr.getAllResponseHeaders = jest.fn().mockReturnValue("Server: \nX-Foo:baz"); - fetch("/foo").then((response) => { - expect(response.headers.get("server")).toEqual(""); - expect(response.headers.get("X-foo")).toEqual("baz"); - }); - - xhr.onload(); - }); - - it("adds and removes the abort event listener", async () => { - const controller = new AbortController(); - jest.spyOn(controller.signal, "addEventListener"); - jest.spyOn(controller.signal, "removeEventListener"); - - fetch("/foo", { - signal: controller.signal, - }) - .then((response) => { - return response.json(); - }) - .then((data) => { - expect(xhr.abort).not.toHaveBeenCalled(); - expect(data).toEqual({ a: "b" }); - expect(controller.signal.addEventListener).toHaveBeenCalledTimes(1); - expect(controller.signal.removeEventListener).toHaveBeenCalledTimes(1); - }); - - xhr.onload(); - }); - - it("adds and removes the abort event listener on abort", async () => { - const controller = new AbortController(); - - jest.spyOn(controller.signal, "addEventListener"); - jest.spyOn(controller.signal, "removeEventListener"); - - fetch("/foo", { - signal: controller.signal, - }).catch((error) => { - expect(error.name).toBe("AbortError"); - expect(xhr.abort).toHaveBeenCalledTimes(1); - expect(controller.signal.addEventListener).toHaveBeenCalledTimes(1); - expect(controller.signal.removeEventListener).toHaveBeenCalledTimes(1); - }); - - controller.abort(); - xhr.onabort(); - }); - - it("adds and removes the abort event listener on error", async () => { - const controller = new AbortController(); - - jest.spyOn(controller.signal, "addEventListener"); - jest.spyOn(controller.signal, "removeEventListener"); - - fetch("/foo", { - signal: controller.signal, - }).catch(() => { - expect(xhr.abort).not.toHaveBeenCalled(); - expect(controller.signal.addEventListener).toHaveBeenCalledTimes(1); - expect(controller.signal.removeEventListener).toHaveBeenCalledTimes(1); - }); - - xhr.onerror(); - }); - }); -}); diff --git a/src/__tests__/jsonexpr/evaluator.test.js b/src/__tests__/jsonexpr/evaluator.test.js deleted file mode 100644 index 53b1927..0000000 --- a/src/__tests__/jsonexpr/evaluator.test.js +++ /dev/null @@ -1,470 +0,0 @@ -import { Evaluator } from "../../jsonexpr/evaluator"; - -describe("Evaluator", () => { - describe("evaluate", () => { - it("should consider an array as implicit AND combinator", () => { - const and = { evaluate: jest.fn().mockReturnValue(true) }; - const or = { evaluate: jest.fn().mockReturnValue(true) }; - - const evaluator = new Evaluator({ and, or }, {}); - const args = [{ value: true }, { value: false }]; - expect(evaluator.evaluate(args)).not.toBe(null); - - expect(and.evaluate).toHaveBeenCalledTimes(1); - expect(and.evaluate).toHaveBeenCalledWith(evaluator, args); - expect(or.evaluate).not.toHaveBeenCalled(); - }); - - it("should return null if operator not found", () => { - const value = { evaluate: jest.fn().mockReturnValue(true) }; - - const evaluator = new Evaluator({ value }, {}); - expect(evaluator.evaluate({ not_found: true })).toBe(null); - - expect(value.evaluate).not.toHaveBeenCalled(); - }); - - it("should call operator evaluate will args", () => { - const value = { evaluate: jest.fn().mockReturnValue(true) }; - - const evaluator = new Evaluator({ value }, {}); - const args = [1, 2, 3]; - expect(evaluator.evaluate({ value: args })).toBe(true); - - expect(value.evaluate).toHaveBeenCalledTimes(1); - expect(value.evaluate).toHaveBeenCalledWith(evaluator, args); - }); - }); - - describe("booleanConvert()", () => { - it("should convert all types of values to boolean", () => { - const evaluator = new Evaluator({}, {}); - - expect(evaluator.booleanConvert({})).toBe(true); - expect(evaluator.booleanConvert([])).toBe(true); - expect(evaluator.booleanConvert(null)).toBe(false); - - expect(evaluator.booleanConvert(true)).toBe(true); - expect(evaluator.booleanConvert(1)).toBe(true); - expect(evaluator.booleanConvert(2)).toBe(true); - expect(evaluator.booleanConvert("abc")).toBe(true); - expect(evaluator.booleanConvert("1")).toBe(true); - - expect(evaluator.booleanConvert(false)).toBe(false); - expect(evaluator.booleanConvert(0)).toBe(false); - expect(evaluator.booleanConvert("")).toBe(false); - expect(evaluator.booleanConvert("0")).toBe(false); - expect(evaluator.booleanConvert("false")).toBe(false); - }); - }); - - describe("numberConvert()", () => { - it("should convert boolean, and numeric strings to number", () => { - const evaluator = new Evaluator({}, {}); - - expect(evaluator.numberConvert(null)).toBe(null); - expect(evaluator.numberConvert({})).toBe(null); - expect(evaluator.numberConvert([])).toBe(null); - expect(evaluator.numberConvert("")).toBe(null); - expect(evaluator.numberConvert("abc")).toBe(null); - expect(evaluator.numberConvert("x1234")).toBe(null); - - expect(evaluator.numberConvert(true)).toBe(1.0); - expect(evaluator.numberConvert(false)).toBe(0.0); - - expect(evaluator.numberConvert(-1.0)).toBe(-1.0); - expect(evaluator.numberConvert(0.0)).toBe(0.0); - expect(evaluator.numberConvert(1.0)).toBe(1.0); - expect(evaluator.numberConvert(1.5)).toBe(1.5); - expect(evaluator.numberConvert(2.0)).toBe(2.0); - expect(evaluator.numberConvert(3.0)).toBe(3.0); - - expect(evaluator.numberConvert(-1)).toBe(-1.0); - expect(evaluator.numberConvert(0)).toBe(0.0); - expect(evaluator.numberConvert(1)).toBe(1.0); - expect(evaluator.numberConvert(2)).toBe(2.0); - expect(evaluator.numberConvert(3)).toBe(3.0); - - expect(evaluator.numberConvert(0x7fffffff)).toBe(2147483647.0); - expect(evaluator.numberConvert(-0x7fffffff)).toBe(-2147483647.0); - expect(evaluator.numberConvert(Number.MAX_SAFE_INTEGER)).toBe(9007199254740991.0); - expect(evaluator.numberConvert(-Number.MAX_SAFE_INTEGER)).toBe(-9007199254740991.0); - - expect(evaluator.numberConvert("-1")).toBe(-1.0); - expect(evaluator.numberConvert("0")).toBe(0.0); - expect(evaluator.numberConvert("1")).toBe(1.0); - expect(evaluator.numberConvert("1.5")).toBe(1.5); - expect(evaluator.numberConvert("2")).toBe(2.0); - expect(evaluator.numberConvert("3.0")).toBe(3.0); - }); - }); - - describe("stringConvert()", () => { - const evaluator = new Evaluator({}, {}); - - expect(evaluator.stringConvert(null)).toBe(null); - expect(evaluator.stringConvert({})).toBe(null); - expect(evaluator.stringConvert([])).toBe(null); - - expect(evaluator.stringConvert(true)).toBe("true"); - expect(evaluator.stringConvert(false)).toBe("false"); - - expect(evaluator.stringConvert("")).toBe(""); - expect(evaluator.stringConvert("abc")).toBe("abc"); - - expect(evaluator.stringConvert(-1.0)).toBe("-1"); - expect(evaluator.stringConvert(0.0)).toBe("0"); - expect(evaluator.stringConvert(1.0)).toBe("1"); - expect(evaluator.stringConvert(2.0)).toBe("2"); - expect(evaluator.stringConvert(3.0)).toBe("3"); - expect(evaluator.stringConvert(2147483647.0)).toBe("2147483647"); - expect(evaluator.stringConvert(-2147483647.0)).toBe("-2147483647"); - expect(evaluator.stringConvert(9007199254740991)).toBe("9007199254740991"); - expect(evaluator.stringConvert(-9007199254740991)).toBe("-9007199254740991"); - expect(evaluator.stringConvert(0.9007199254740991)).toBe("0.900719925474099"); - expect(evaluator.stringConvert(-0.9007199254740991)).toBe("-0.900719925474099"); - expect(evaluator.stringConvert(-1)).toBe("-1"); - expect(evaluator.stringConvert(0)).toBe("0"); - expect(evaluator.stringConvert(1)).toBe("1"); - expect(evaluator.stringConvert(2)).toBe("2"); - expect(evaluator.stringConvert(3)).toBe("3"); - expect(evaluator.stringConvert(2147483647)).toBe("2147483647"); - expect(evaluator.stringConvert(-2147483647)).toBe("-2147483647"); - expect(evaluator.stringConvert(9007199254740991)).toBe("9007199254740991"); - expect(evaluator.stringConvert(-9007199254740991)).toBe("-9007199254740991"); - }); - - describe("extractVar()", () => { - it("should find data by paths delimited by /", () => { - const vars = { - a: 1, - b: true, - c: false, - d: [1, 2, 3], - e: [1, { z: 2 }, 3], - f: { y: { x: 3, 0: 10 } }, - }; - - const evaluator = new Evaluator({}, vars); - - expect(evaluator.extractVar("a")).toBe(1); - expect(evaluator.extractVar("b")).toBe(true); - expect(evaluator.extractVar("c")).toBe(false); - expect(evaluator.extractVar("d")).toEqual([1, 2, 3]); - expect(evaluator.extractVar("e")).toEqual([1, { z: 2 }, 3]); - expect(evaluator.extractVar("f")).toEqual({ y: { x: 3, 0: 10 } }); - - expect(evaluator.extractVar("a/0")).toBe(null); - expect(evaluator.extractVar("a/b")).toBe(null); - expect(evaluator.extractVar("b/0")).toBe(null); - expect(evaluator.extractVar("b/e")).toBe(null); - - expect(evaluator.extractVar("d/0")).toBe(1); - expect(evaluator.extractVar("d/1")).toBe(2); - expect(evaluator.extractVar("d/2")).toBe(3); - expect(evaluator.extractVar("d/3")).toBe(null); - - expect(evaluator.extractVar("e/0")).toBe(1); - expect(evaluator.extractVar("e/1/z")).toBe(2); - expect(evaluator.extractVar("e/2")).toBe(3); - expect(evaluator.extractVar("e/1/0")).toBe(null); - - expect(evaluator.extractVar("f/y")).toMatchObject({ x: 3 }); - expect(evaluator.extractVar("f/y/x")).toBe(3); - expect(evaluator.extractVar("f/y/0")).toBe(10); - }); - }); - - describe("compare()", () => { - it("should return null if comparing non-null with null", () => { - const evaluator = new Evaluator({}, {}); - - expect(evaluator.compare(null, null)).toBe(0); - - expect(evaluator.compare(null, 0)).toBe(null); - expect(evaluator.compare(null, 1)).toBe(null); - expect(evaluator.compare(null, true)).toBe(null); - expect(evaluator.compare(null, false)).toBe(null); - expect(evaluator.compare(null, "")).toBe(null); - expect(evaluator.compare(null, "abc")).toBe(null); - expect(evaluator.compare(null, {})).toBe(null); - expect(evaluator.compare(null, [])).toBe(null); - - expect(evaluator.compare(0, null)).toBe(null); - expect(evaluator.compare(1, null)).toBe(null); - expect(evaluator.compare(true, null)).toBe(null); - expect(evaluator.compare(false, null)).toBe(null); - expect(evaluator.compare("", null)).toBe(null); - expect(evaluator.compare("abc", null)).toBe(null); - expect(evaluator.compare({}, null)).toBe(null); - expect(evaluator.compare([], null)).toBe(null); - }); - - it("should return null if comparing non-object with object", () => { - const evaluator = new Evaluator({}, {}); - - expect(evaluator.compare({}, 0)).toBe(null); - expect(evaluator.compare({}, 1)).toBe(null); - expect(evaluator.compare({}, true)).toBe(null); - expect(evaluator.compare({}, false)).toBe(null); - expect(evaluator.compare({}, "")).toBe(null); - expect(evaluator.compare({}, "abc")).toBe(null); - expect(evaluator.compare({}, {})).toBe(0); - expect(evaluator.compare({ a: 1 }, { a: 1 })).toBe(0); - expect(evaluator.compare({ a: 1 }, { b: 2 })).toBe(null); - expect(evaluator.compare({}, [])).toBe(null); - - expect(evaluator.compare([], 0)).toBe(null); - expect(evaluator.compare([], 1)).toBe(null); - expect(evaluator.compare([], true)).toBe(null); - expect(evaluator.compare([], false)).toBe(null); - expect(evaluator.compare([], "")).toBe(null); - expect(evaluator.compare([], "abc")).toBe(null); - expect(evaluator.compare([], {})).toBe(null); - expect(evaluator.compare([], [])).toBe(0); - expect(evaluator.compare([1, 2], [1, 2])).toBe(0); - expect(evaluator.compare([1, 2], [3, 4])).toBe(null); - }); - - it("should coerce right-side argument to boolean and compare", () => { - const evaluator = new Evaluator({}, {}); - - expect(evaluator.compare(false, 0)).toBe(0); - expect(evaluator.compare(false, 1)).toBe(-1); - expect(evaluator.compare(false, true)).toBe(-1); - expect(evaluator.compare(false, false)).toBe(0); - expect(evaluator.compare(false, "")).toBe(0); - expect(evaluator.compare(false, "abc")).toBe(-1); - expect(evaluator.compare(false, {})).toBe(-1); - expect(evaluator.compare(false, [])).toBe(-1); - - expect(evaluator.compare(true, 0)).toBe(1); - expect(evaluator.compare(true, 1)).toBe(0); - expect(evaluator.compare(true, true)).toBe(0); - expect(evaluator.compare(true, false)).toBe(1); - expect(evaluator.compare(true, "")).toBe(1); - expect(evaluator.compare(true, "abc")).toBe(0); - expect(evaluator.compare(true, {})).toBe(0); - expect(evaluator.compare(true, [])).toBe(0); - }); - - it("should coerce right-side argument to boolean and compare", () => { - const evaluator = new Evaluator({}, {}); - - expect(evaluator.compare(0, 0)).toBe(0); - expect(evaluator.compare(0, 1)).toBe(-1); - expect(evaluator.compare(0, true)).toBe(-1); - expect(evaluator.compare(0, false)).toBe(0); - expect(evaluator.compare(0, "")).toBe(null); - expect(evaluator.compare(0, "abc")).toBe(null); - expect(evaluator.compare(0, {})).toBe(null); - expect(evaluator.compare(0, [])).toBe(null); - - expect(evaluator.compare(1, 0)).toBe(1); - expect(evaluator.compare(1, 1)).toBe(0); - expect(evaluator.compare(1, true)).toBe(0); - expect(evaluator.compare(1, false)).toBe(1); - expect(evaluator.compare(1, "")).toBe(null); - expect(evaluator.compare(1, "abc")).toBe(null); - expect(evaluator.compare(1, {})).toBe(null); - expect(evaluator.compare(1, [])).toBe(null); - - expect(evaluator.compare(1.0, 1)).toBe(0); - expect(evaluator.compare(1.5, 1)).toBe(1); - expect(evaluator.compare(2.0, 1)).toBe(1); - expect(evaluator.compare(3.0, 1)).toBe(1); - - expect(evaluator.compare(1, 1.0)).toBe(0); - expect(evaluator.compare(1, 1.5)).toBe(-1); - expect(evaluator.compare(1, 2.0)).toBe(-1); - expect(evaluator.compare(1, 3.0)).toBe(-1); - - expect(evaluator.compare(9007199254740991, 9007199254740991)).toBe(0); - expect(evaluator.compare(0, 9007199254740991)).toBe(-1); - expect(evaluator.compare(9007199254740991, 0)).toBe(1); - - expect(evaluator.compare(9007199254740991.0, 9007199254740991.0)).toBe(0); - expect(evaluator.compare(0, 9007199254740991.0)).toBe(-1); - expect(evaluator.compare(9007199254740991.0, 0)).toBe(1); - }); - - it("should coerce right-hand side argument to string and compare", () => { - const evaluator = new Evaluator({}, {}); - - expect(evaluator.compare("", "")).toBe(0); - expect(evaluator.compare("abc", "abc")).toBe(0); - expect(evaluator.compare("0", 0)).toBe(0); - expect(evaluator.compare("1", 1)).toBe(0); - expect(evaluator.compare("true", true)).toBe(0); - expect(evaluator.compare("false", false)).toBe(0); - expect(evaluator.compare("", {})).toBe(null); - expect(evaluator.compare("abc", {})).toBe(null); - expect(evaluator.compare("", [])).toBe(null); - expect(evaluator.compare("abc", [])).toBe(null); - - expect(evaluator.compare("abc", "bcd")).toBe(-1); - expect(evaluator.compare("bcd", "abc")).toBe(1); - expect(evaluator.compare("0", "1")).toBe(-1); - expect(evaluator.compare("1", "0")).toBe(1); - expect(evaluator.compare("9", "100")).toBe(1); - expect(evaluator.compare("100", "9")).toBe(-1); - }); - }); - - describe("versionCompare()", () => { - it("should return 0 for equal versions", () => { - const evaluator = new Evaluator({}, {}); - - expect(evaluator.versionCompare("1.0.0", "1.0.0")).toBe(0); - expect(evaluator.versionCompare("0.0.0", "0.0.0")).toBe(0); - expect(evaluator.versionCompare("999.999.999", "999.999.999")).toBe(0); - }); - - it("should compare major versions", () => { - const evaluator = new Evaluator({}, {}); - - expect(evaluator.versionCompare("2.0.0", "1.0.0")).toBe(1); - expect(evaluator.versionCompare("1.0.0", "2.0.0")).toBe(-1); - }); - - it("should compare minor versions", () => { - const evaluator = new Evaluator({}, {}); - - expect(evaluator.versionCompare("1.2.0", "1.1.0")).toBe(1); - expect(evaluator.versionCompare("1.1.0", "1.2.0")).toBe(-1); - }); - - it("should compare patch versions", () => { - const evaluator = new Evaluator({}, {}); - - expect(evaluator.versionCompare("1.0.2", "1.0.1")).toBe(1); - expect(evaluator.versionCompare("1.0.1", "1.0.2")).toBe(-1); - }); - - it("should compare numerically not lexicographically", () => { - const evaluator = new Evaluator({}, {}); - - expect(evaluator.versionCompare("1.10.0", "1.9.0")).toBe(1); - expect(evaluator.versionCompare("1.9.0", "1.10.0")).toBe(-1); - expect(evaluator.versionCompare("10.0.0", "9.0.0")).toBe(1); - }); - - it("should treat missing parts as 0", () => { - const evaluator = new Evaluator({}, {}); - - expect(evaluator.versionCompare("1.2", "1.2.0")).toBe(0); - expect(evaluator.versionCompare("1", "1.0.0")).toBe(0); - expect(evaluator.versionCompare("1.2.0", "1.2")).toBe(0); - expect(evaluator.versionCompare("1.0.0", "1")).toBe(0); - }); - - it("should handle leading zeros in version parts", () => { - const evaluator = new Evaluator({}, {}); - - expect(evaluator.versionCompare("1.02.0", "1.2.0")).toBe(0); - expect(evaluator.versionCompare("1.002.030", "1.2.30")).toBe(0); - expect(evaluator.versionCompare("01.0.0", "1.0.0")).toBe(0); - expect(evaluator.versionCompare("1.02.0", "1.3.0")).toBe(-1); - }); - - it("should handle pre-release versions", () => { - const evaluator = new Evaluator({}, {}); - - expect(evaluator.versionCompare("1.0.0", "1.0.0-alpha")).toBe(1); - expect(evaluator.versionCompare("1.0.0-alpha", "1.0.0")).toBe(-1); - expect(evaluator.versionCompare("1.0.0-alpha", "1.0.0-alpha")).toBe(0); - expect(evaluator.versionCompare("1.0.0-alpha", "1.0.0-beta")).toBe(-1); - expect(evaluator.versionCompare("1.0.0-beta", "1.0.0-alpha")).toBe(1); - expect(evaluator.versionCompare("1.0.0-alpha.1", "1.0.0-alpha.2")).toBe(-1); - expect(evaluator.versionCompare("1.0.0-alpha.2", "1.0.0-alpha.1")).toBe(1); - expect(evaluator.versionCompare("1.0.0-1", "1.0.0-2")).toBe(-1); - expect(evaluator.versionCompare("1.0.0-2", "1.0.0-1")).toBe(1); - }); - - it("should compare numeric pre-release identifiers as less than string identifiers", () => { - const evaluator = new Evaluator({}, {}); - - expect(evaluator.versionCompare("1.0.0-1", "1.0.0-alpha")).toBe(-1); - expect(evaluator.versionCompare("1.0.0-alpha", "1.0.0-1")).toBe(1); - }); - - it("should compare pre-release with fewer identifiers as less", () => { - const evaluator = new Evaluator({}, {}); - - expect(evaluator.versionCompare("1.0.0-alpha", "1.0.0-alpha.1")).toBe(-1); - expect(evaluator.versionCompare("1.0.0-alpha.1", "1.0.0-alpha")).toBe(1); - }); - - it("should strip v prefix", () => { - const evaluator = new Evaluator({}, {}); - - expect(evaluator.versionCompare("v1.0.0", "1.0.0")).toBe(0); - expect(evaluator.versionCompare("V1.0.0", "1.0.0")).toBe(0); - expect(evaluator.versionCompare("v1.0.0", "V1.0.0")).toBe(0); - }); - - it("should return null for null inputs", () => { - const evaluator = new Evaluator({}, {}); - - expect(evaluator.versionCompare(null, "1.0.0")).toBe(null); - expect(evaluator.versionCompare("1.0.0", null)).toBe(null); - expect(evaluator.versionCompare(null, null)).toBe(null); - }); - - it("should coerce non-string inputs via stringConvert", () => { - const evaluator = new Evaluator({}, {}); - - expect(evaluator.versionCompare(1, "1.0.0")).toBe(0); - expect(evaluator.versionCompare("1.0.0", 1)).toBe(0); - expect(evaluator.versionCompare(true, "true")).toBe(0); - }); - - it("should return null for unconvertible inputs", () => { - const evaluator = new Evaluator({}, {}); - - expect(evaluator.versionCompare({}, "1.0.0")).toBe(null); - expect(evaluator.versionCompare("1.0.0", {})).toBe(null); - expect(evaluator.versionCompare([], "1.0.0")).toBe(null); - }); - - it("should ignore build metadata", () => { - const evaluator = new Evaluator({}, {}); - - expect(evaluator.versionCompare("1.0.0+build1", "1.0.0+build2")).toBe(0); - expect(evaluator.versionCompare("1.0.0+build1", "1.0.0")).toBe(0); - }); - - it("should handle pre-release combined with build metadata", () => { - const evaluator = new Evaluator({}, {}); - - expect(evaluator.versionCompare("1.0.0-alpha+build1", "1.0.0-alpha+build2")).toBe(0); - expect(evaluator.versionCompare("1.0.0-alpha+build1", "1.0.0-beta")).toBe(-1); - expect(evaluator.versionCompare("1.0.0-alpha+build1", "1.0.0")).toBe(-1); - }); - - it("should return null for empty string inputs", () => { - const evaluator = new Evaluator({}, {}); - - expect(evaluator.versionCompare("", "1.0.0")).toBe(null); - expect(evaluator.versionCompare("1.0.0", "")).toBe(null); - expect(evaluator.versionCompare("", "")).toBe(null); - }); - - it("should return null for undefined inputs", () => { - const evaluator = new Evaluator({}, {}); - - expect(evaluator.versionCompare(undefined, "1.0.0")).toBe(null); - expect(evaluator.versionCompare("1.0.0", undefined)).toBe(null); - }); - - it("should return null for inputs that normalize to empty core", () => { - const evaluator = new Evaluator({}, {}); - - expect(evaluator.versionCompare("v", "1.0.0")).toBe(null); - expect(evaluator.versionCompare("V", "1.0.0")).toBe(null); - expect(evaluator.versionCompare("+build", "1.0.0")).toBe(null); - expect(evaluator.versionCompare("v+build", "1.0.0")).toBe(null); - expect(evaluator.versionCompare("1.0.0", "v")).toBe(null); - }); - }); -}); diff --git a/src/__tests__/jsonexpr/jsonexpr.test.js b/src/__tests__/jsonexpr/jsonexpr.test.js deleted file mode 100644 index b8bd3cf..0000000 --- a/src/__tests__/jsonexpr/jsonexpr.test.js +++ /dev/null @@ -1,69 +0,0 @@ -import { JsonExpr } from "../../jsonexpr/jsonexpr"; - -describe("jsonexpr", () => { - const valueFor = (x) => ({ value: x }); - const varFor = (p) => ({ var: { path: p } }); - const unaryOp = (op, arg) => ({ [op]: arg }); - const binaryOp = (op, arg0, arg1) => ({ [op]: [arg0, arg1] }); - - const jsonExpr = new JsonExpr(); - - const John = { age: 20, language: "en-US", returning: false }; - const Terry = { age: 20, language: "en-GB", returning: true }; - const Kate = { age: 50, language: "es-ES", returning: false }; - const Maria = { age: 52, language: "pt-PT", returning: true }; - - const AgeTwentyAndUS = [ - binaryOp("eq", varFor("age"), valueFor(20)), - binaryOp("eq", varFor("language"), valueFor("en-US")), - ]; - const AgeOverFifty = [binaryOp("gte", varFor("age"), valueFor(50))]; - const AgeTwentyAndUS_Or_AgeOverFifty = [{ or: [AgeTwentyAndUS, AgeOverFifty] }]; - const Returning = [binaryOp("eq", varFor("returning"), valueFor(true))]; - const Returning_And_AgeTwentyAndUS_Or_AgeOverFifty = [Returning, AgeTwentyAndUS_Or_AgeOverFifty]; - const NotReturning_And_Spanish = [unaryOp("not", Returning), binaryOp("eq", varFor("language"), valueFor("es-ES"))]; - - describe("evaluateBooleanExpr()", () => { - test("AgeTwentyAndUS", () => { - expect(jsonExpr.evaluateBooleanExpr(AgeTwentyAndUS, John)).toEqual(true); - expect(jsonExpr.evaluateBooleanExpr(AgeTwentyAndUS, Terry)).toEqual(false); - expect(jsonExpr.evaluateBooleanExpr(AgeTwentyAndUS, Kate)).toEqual(false); - expect(jsonExpr.evaluateBooleanExpr(AgeTwentyAndUS, Maria)).toEqual(false); - }); - - test("AgeOverFifty", () => { - expect(jsonExpr.evaluateBooleanExpr(AgeOverFifty, John)).toEqual(false); - expect(jsonExpr.evaluateBooleanExpr(AgeOverFifty, Terry)).toEqual(false); - expect(jsonExpr.evaluateBooleanExpr(AgeOverFifty, Kate)).toEqual(true); - expect(jsonExpr.evaluateBooleanExpr(AgeOverFifty, Maria)).toEqual(true); - }); - - test("AgeTwentyAndUS_Or_AgeOverFifty", () => { - expect(jsonExpr.evaluateBooleanExpr(AgeTwentyAndUS_Or_AgeOverFifty, John)).toEqual(true); - expect(jsonExpr.evaluateBooleanExpr(AgeTwentyAndUS_Or_AgeOverFifty, Terry)).toEqual(false); - expect(jsonExpr.evaluateBooleanExpr(AgeTwentyAndUS_Or_AgeOverFifty, Kate)).toEqual(true); - expect(jsonExpr.evaluateBooleanExpr(AgeTwentyAndUS_Or_AgeOverFifty, Maria)).toEqual(true); - }); - - test("Returning", () => { - expect(jsonExpr.evaluateBooleanExpr(Returning, John)).toEqual(false); - expect(jsonExpr.evaluateBooleanExpr(Returning, Terry)).toEqual(true); - expect(jsonExpr.evaluateBooleanExpr(Returning, Kate)).toEqual(false); - expect(jsonExpr.evaluateBooleanExpr(Returning, Maria)).toEqual(true); - }); - - test("Returning_And_AgeTwentyAndUS_Or_AgeOverFifty", () => { - expect(jsonExpr.evaluateBooleanExpr(Returning_And_AgeTwentyAndUS_Or_AgeOverFifty, John)).toEqual(false); - expect(jsonExpr.evaluateBooleanExpr(Returning_And_AgeTwentyAndUS_Or_AgeOverFifty, Terry)).toEqual(false); - expect(jsonExpr.evaluateBooleanExpr(Returning_And_AgeTwentyAndUS_Or_AgeOverFifty, Kate)).toEqual(false); - expect(jsonExpr.evaluateBooleanExpr(Returning_And_AgeTwentyAndUS_Or_AgeOverFifty, Maria)).toEqual(true); - }); - - test("NotReturning_And_Spanish", () => { - expect(jsonExpr.evaluateBooleanExpr(NotReturning_And_Spanish, John)).toEqual(false); - expect(jsonExpr.evaluateBooleanExpr(NotReturning_And_Spanish, Terry)).toEqual(false); - expect(jsonExpr.evaluateBooleanExpr(NotReturning_And_Spanish, Kate)).toEqual(true); - expect(jsonExpr.evaluateBooleanExpr(NotReturning_And_Spanish, Maria)).toEqual(false); - }); - }); -}); diff --git a/src/__tests__/jsonexpr/operators/and.test.js b/src/__tests__/jsonexpr/operators/and.test.js deleted file mode 100644 index 6b8dfd5..0000000 --- a/src/__tests__/jsonexpr/operators/and.test.js +++ /dev/null @@ -1,54 +0,0 @@ -import { AndCombinator } from "../../../jsonexpr/operators/and"; -import { mockEvaluator } from "./evaluator"; - -describe("AndCombinator", () => { - const combinator = new AndCombinator(); - - describe("evaluate", () => { - const evaluator = mockEvaluator(); - - it("should return true if all arguments evaluate to true", () => { - expect(combinator.evaluate(evaluator, [true])).toBe(true); - expect(evaluator.evaluate).toHaveBeenCalledTimes(1); - expect(evaluator.evaluate).toHaveBeenCalledWith(true); - expect(evaluator.booleanConvert).toHaveBeenCalledTimes(1); - expect(evaluator.booleanConvert).toHaveBeenCalledWith(true); - }); - - it("should return false if any argument evaluates to false", () => { - expect(combinator.evaluate(evaluator, [false])).toBe(false); - expect(evaluator.evaluate).toHaveBeenCalledTimes(1); - expect(evaluator.evaluate).toHaveBeenCalledWith(false); - expect(evaluator.booleanConvert).toHaveBeenCalledTimes(1); - expect(evaluator.booleanConvert).toHaveBeenCalledWith(false); - }); - - it("should return false if any argument evaluates to null", () => { - expect(combinator.evaluate(evaluator, [null])).toBe(false); - expect(evaluator.evaluate).toHaveBeenCalledTimes(1); - expect(evaluator.evaluate).toHaveBeenCalledWith(null); - expect(evaluator.booleanConvert).toHaveBeenCalledTimes(1); - expect(evaluator.booleanConvert).toHaveBeenCalledWith(null); - }); - - it("should short-circuit and not evaluate unnecessary expressions", () => { - expect(combinator.evaluate(evaluator, [true, false, true])).toBe(false); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.booleanConvert).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, true); - expect(evaluator.booleanConvert).toHaveBeenNthCalledWith(1, true); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, false); - expect(evaluator.booleanConvert).toHaveBeenNthCalledWith(2, false); - }); - - it("should combine multiple arguments", () => { - expect(combinator.evaluate(evaluator, [true, true])).toBe(true); - expect(combinator.evaluate(evaluator, [true, true, true])).toBe(true); - - expect(combinator.evaluate(evaluator, [true, false])).toBe(false); - expect(combinator.evaluate(evaluator, [false, true])).toBe(false); - expect(combinator.evaluate(evaluator, [false, false])).toBe(false); - expect(combinator.evaluate(evaluator, [false, false, false])).toBe(false); - }); - }); -}); diff --git a/src/__tests__/jsonexpr/operators/eq.test.js b/src/__tests__/jsonexpr/operators/eq.test.js deleted file mode 100644 index d7132af..0000000 --- a/src/__tests__/jsonexpr/operators/eq.test.js +++ /dev/null @@ -1,110 +0,0 @@ -import { mockEvaluator } from "./evaluator"; -import { EqualsOperator } from "../../../jsonexpr/operators/eq"; - -describe("EqOperator", () => { - const operator = new EqualsOperator(); - - describe("evaluate", () => { - const evaluator = mockEvaluator(); - - it("should return true when arguments are equal and comparable", () => { - expect(operator.evaluate(evaluator, [0, 0])).toBe(true); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, 0); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, 0); - expect(evaluator.compare).toHaveBeenCalledTimes(1); - expect(evaluator.compare).toHaveBeenCalledWith(0, 0); - - evaluator.evaluate.mockClear(); - evaluator.compare.mockClear(); - - expect(operator.evaluate(evaluator, [1, 0])).toBe(false); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, 1); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, 0); - expect(evaluator.compare).toHaveBeenCalledTimes(1); - expect(evaluator.compare).toHaveBeenCalledWith(1, 0); - - evaluator.evaluate.mockClear(); - evaluator.compare.mockClear(); - - expect(operator.evaluate(evaluator, [0, 1])).toBe(false); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, 0); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, 1); - expect(evaluator.compare).toHaveBeenCalledTimes(1); - expect(evaluator.compare).toHaveBeenCalledWith(0, 1); - - evaluator.evaluate.mockClear(); - evaluator.compare.mockClear(); - - expect(operator.evaluate(evaluator, [null, null])).toBe(null); - expect(evaluator.evaluate).toHaveBeenCalledTimes(1); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, null); - expect(evaluator.compare).toHaveBeenCalledTimes(0); - - evaluator.evaluate.mockClear(); - evaluator.compare.mockClear(); - - expect( - operator.evaluate(evaluator, [ - [1, 2], - [1, 2], - ]) - ).toBe(true); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, [1, 2]); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, [1, 2]); - expect(evaluator.compare).toHaveBeenCalledTimes(1); - expect(evaluator.compare).toHaveBeenCalledWith([1, 2], [1, 2]); - - evaluator.evaluate.mockClear(); - evaluator.compare.mockClear(); - - expect( - operator.evaluate(evaluator, [ - [1, 2], - [3, 4], - ]) - ).toBe(null); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, [1, 2]); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, [3, 4]); - expect(evaluator.compare).toHaveBeenCalledTimes(1); - expect(evaluator.compare).toHaveBeenCalledWith([1, 2], [3, 4]); - - evaluator.evaluate.mockClear(); - evaluator.compare.mockClear(); - - expect( - operator.evaluate(evaluator, [ - { a: 1, b: 2 }, - { a: 1, b: 2 }, - ]) - ).toBe(true); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, { a: 1, b: 2 }); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, { a: 1, b: 2 }); - expect(evaluator.compare).toHaveBeenCalledTimes(1); - expect(evaluator.compare).toHaveBeenCalledWith({ a: 1, b: 2 }, { a: 1, b: 2 }); - - evaluator.evaluate.mockClear(); - evaluator.compare.mockClear(); - - expect( - operator.evaluate(evaluator, [ - { a: 1, b: 2 }, - { a: 3, b: 4 }, - ]) - ).toBe(null); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, { a: 1, b: 2 }); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, { a: 3, b: 4 }); - expect(evaluator.compare).toHaveBeenCalledTimes(1); - expect(evaluator.compare).toHaveBeenCalledWith({ a: 1, b: 2 }, { a: 3, b: 4 }); - - evaluator.evaluate.mockClear(); - evaluator.compare.mockClear(); - }); - }); -}); diff --git a/src/__tests__/jsonexpr/operators/evaluator.js b/src/__tests__/jsonexpr/operators/evaluator.js deleted file mode 100644 index 2c67b58..0000000 --- a/src/__tests__/jsonexpr/operators/evaluator.js +++ /dev/null @@ -1,62 +0,0 @@ -import { isEqualsDeep, isObject } from "../../../utils"; - -export function mockEvaluator() { - return { - evaluate: jest.fn((expr) => { - return expr; - }), - - booleanConvert: jest.fn((expr) => { - return expr; - }), - - numberConvert: jest.fn((expr) => { - return expr; - }), - - stringConvert: jest.fn((expr) => { - return expr; - }), - - versionCompare: jest.fn((lhs, rhs) => { - const lhsStr = typeof lhs === "string" ? lhs : null; - const rhsStr = typeof rhs === "string" ? rhs : null; - if (lhsStr === null || rhsStr === null) return null; - const lParts = lhsStr.split(".").map(Number); - const rParts = rhsStr.split(".").map(Number); - const len = Math.max(lParts.length, rParts.length); - for (let i = 0; i < len; i++) { - const l = lParts[i] || 0; - const r = rParts[i] || 0; - if (l !== r) return l > r ? 1 : -1; - } - return 0; - }), - - compare: jest.fn((lhs, rhs) => { - switch (typeof lhs) { - case "boolean": - case "number": - case "string": - return lhs === rhs ? 0 : lhs > rhs ? 1 : -1; - default: - if (isObject(lhs) || Array.isArray(lhs)) { - if (isEqualsDeep(lhs, rhs)) { - return 0; - } - } - break; - } - return null; - }), - - extractVar: jest.fn((path) => { - switch (path) { - case "a/b/c": - return "abc"; - default: - return null; - } - }), - }; -} diff --git a/src/__tests__/jsonexpr/operators/gt.test.js b/src/__tests__/jsonexpr/operators/gt.test.js deleted file mode 100644 index e134f72..0000000 --- a/src/__tests__/jsonexpr/operators/gt.test.js +++ /dev/null @@ -1,50 +0,0 @@ -import { mockEvaluator } from "./evaluator"; -import { GreaterThanOperator } from "../../../jsonexpr/operators/gt"; - -describe("GreaterThanOperator", () => { - const operator = new GreaterThanOperator(); - - describe("evaluate", () => { - const evaluator = mockEvaluator(); - - it("should return true when left-side argument is greater and comparable", () => { - expect(operator.evaluate(evaluator, [0, 0])).toBe(false); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, 0); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, 0); - expect(evaluator.compare).toHaveBeenCalledTimes(1); - expect(evaluator.compare).toHaveBeenCalledWith(0, 0); - - evaluator.evaluate.mockClear(); - evaluator.compare.mockClear(); - - expect(operator.evaluate(evaluator, [1, 0])).toBe(true); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, 1); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, 0); - expect(evaluator.compare).toHaveBeenCalledTimes(1); - expect(evaluator.compare).toHaveBeenCalledWith(1, 0); - - evaluator.evaluate.mockClear(); - evaluator.compare.mockClear(); - - expect(operator.evaluate(evaluator, [0, 1])).toBe(false); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, 0); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, 1); - expect(evaluator.compare).toHaveBeenCalledTimes(1); - expect(evaluator.compare).toHaveBeenCalledWith(0, 1); - - evaluator.evaluate.mockClear(); - evaluator.compare.mockClear(); - - expect(operator.evaluate(evaluator, [null, null])).toBe(null); - expect(evaluator.evaluate).toHaveBeenCalledTimes(1); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, null); - expect(evaluator.compare).toHaveBeenCalledTimes(0); - - evaluator.evaluate.mockClear(); - evaluator.compare.mockClear(); - }); - }); -}); diff --git a/src/__tests__/jsonexpr/operators/gte.test.js b/src/__tests__/jsonexpr/operators/gte.test.js deleted file mode 100644 index 4adbbea..0000000 --- a/src/__tests__/jsonexpr/operators/gte.test.js +++ /dev/null @@ -1,50 +0,0 @@ -import { mockEvaluator } from "./evaluator"; -import { GreaterThanOrEqualOperator } from "../../../jsonexpr/operators/gte"; - -describe("GreaterThanOrEqualOperator", () => { - const operator = new GreaterThanOrEqualOperator(); - - describe("evaluate", () => { - const evaluator = mockEvaluator(); - - it("should return true when left-side argument is greater or equal and comparable", () => { - expect(operator.evaluate(evaluator, [0, 0])).toBe(true); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, 0); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, 0); - expect(evaluator.compare).toHaveBeenCalledTimes(1); - expect(evaluator.compare).toHaveBeenCalledWith(0, 0); - - evaluator.evaluate.mockClear(); - evaluator.compare.mockClear(); - - expect(operator.evaluate(evaluator, [1, 0])).toBe(true); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, 1); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, 0); - expect(evaluator.compare).toHaveBeenCalledTimes(1); - expect(evaluator.compare).toHaveBeenCalledWith(1, 0); - - evaluator.evaluate.mockClear(); - evaluator.compare.mockClear(); - - expect(operator.evaluate(evaluator, [0, 1])).toBe(false); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, 0); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, 1); - expect(evaluator.compare).toHaveBeenCalledTimes(1); - expect(evaluator.compare).toHaveBeenCalledWith(0, 1); - - evaluator.evaluate.mockClear(); - evaluator.compare.mockClear(); - - expect(operator.evaluate(evaluator, [null, null])).toBe(null); - expect(evaluator.evaluate).toHaveBeenCalledTimes(1); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, null); - expect(evaluator.compare).toHaveBeenCalledTimes(0); - - evaluator.evaluate.mockClear(); - evaluator.compare.mockClear(); - }); - }); -}); diff --git a/src/__tests__/jsonexpr/operators/in.test.js b/src/__tests__/jsonexpr/operators/in.test.js deleted file mode 100644 index 27fcef5..0000000 --- a/src/__tests__/jsonexpr/operators/in.test.js +++ /dev/null @@ -1,178 +0,0 @@ -import { mockEvaluator } from "./evaluator"; -import { InOperator } from "../../../jsonexpr/operators/in"; - -describe("InOperator", () => { - const operator = new InOperator(); - - describe("evaluate", () => { - const evaluator = mockEvaluator(); - - it("should return true if string contains needle", () => { - expect(operator.evaluate(evaluator, ["abcdefghijk", "abc"])).toBe(true); - expect(operator.evaluate(evaluator, ["abcdefghijk", "def"])).toBe(true); - expect(operator.evaluate(evaluator, ["abcdefghijk", "xxx"])).toBe(false); - - expect(operator.evaluate(evaluator, ["abcdefghijk", null])).toBe(null); - expect(operator.evaluate(evaluator, [null, "abc"])).toBe(null); - - expect(evaluator.evaluate).toHaveBeenCalledTimes(9); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, "abcdefghijk"); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, "abc"); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(3, "abcdefghijk"); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(4, "def"); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(5, "abcdefghijk"); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(6, "xxx"); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(7, "abcdefghijk"); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(8, null); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(9, null); - - expect(evaluator.stringConvert).toHaveBeenCalledTimes(3); - expect(evaluator.stringConvert).toHaveBeenNthCalledWith(1, "abc"); - expect(evaluator.stringConvert).toHaveBeenNthCalledWith(2, "def"); - expect(evaluator.stringConvert).toHaveBeenNthCalledWith(3, "xxx"); - }); - - it("should return false with empty array", () => { - expect(operator.evaluate(evaluator, [[], 1])).toBe(false); - expect(operator.evaluate(evaluator, [[], "1"])).toBe(false); - expect(operator.evaluate(evaluator, [[], true])).toBe(false); - expect(operator.evaluate(evaluator, [[], false])).toBe(false); - expect(operator.evaluate(evaluator, [[], null])).toBe(null); - - expect(evaluator.evaluate).toHaveBeenCalledTimes(10); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, []); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, 1); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(3, []); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(4, "1"); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(5, []); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(6, true); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(7, []); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(8, false); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(9, []); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(10, null); - - expect(evaluator.booleanConvert).not.toHaveBeenCalled(); - expect(evaluator.numberConvert).not.toHaveBeenCalled(); - expect(evaluator.stringConvert).not.toHaveBeenCalled(); - expect(evaluator.compare).not.toHaveBeenCalled(); - }); - - it("should compare array elements as left-side and needle as right-side", () => { - const haystack01 = [0, 1]; - const haystack12 = [1, 2]; - - expect(operator.evaluate(evaluator, [haystack01, 2])).toBe(false); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, haystack01); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, 2); - expect(evaluator.compare).toHaveBeenCalledTimes(2); - expect(evaluator.compare).toHaveBeenNthCalledWith(1, 0, 2); - expect(evaluator.compare).toHaveBeenNthCalledWith(2, 1, 2); - - evaluator.evaluate.mockClear(); - evaluator.compare.mockClear(); - - expect(operator.evaluate(evaluator, [haystack12, 0])).toBe(false); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, haystack12); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, 0); - expect(evaluator.compare).toHaveBeenCalledTimes(2); - expect(evaluator.compare).toHaveBeenNthCalledWith(1, 1, 0); - expect(evaluator.compare).toHaveBeenNthCalledWith(2, 2, 0); - - evaluator.evaluate.mockClear(); - evaluator.compare.mockClear(); - - expect(operator.evaluate(evaluator, [haystack12, 1])).toBe(true); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, haystack12); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, 1); - expect(evaluator.compare).toHaveBeenCalledTimes(1); - expect(evaluator.compare).toHaveBeenNthCalledWith(1, 1, 1); - - evaluator.evaluate.mockClear(); - evaluator.compare.mockClear(); - - expect(operator.evaluate(evaluator, [haystack12, 2])).toBe(true); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, haystack12); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, 2); - expect(evaluator.compare).toHaveBeenCalledTimes(2); - expect(evaluator.compare).toHaveBeenNthCalledWith(1, 1, 2); - expect(evaluator.compare).toHaveBeenNthCalledWith(2, 2, 2); - }); - - it("should return true if object contains key", () => { - const haystackab = { a: 1, b: 2 }; - const haystackbc = { b: 2, c: 3, 0: 100 }; - - expect(operator.evaluate(evaluator, [haystackab, "c"])).toBe(false); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, haystackab); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, "c"); - expect(evaluator.stringConvert).toHaveBeenCalledTimes(1); - expect(evaluator.stringConvert).toHaveBeenCalledWith("c"); - - evaluator.evaluate.mockClear(); - evaluator.stringConvert.mockClear(); - - expect(operator.evaluate(evaluator, [haystackbc, "a"])).toBe(false); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, haystackbc); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, "a"); - expect(evaluator.stringConvert).toHaveBeenCalledTimes(1); - expect(evaluator.stringConvert).toHaveBeenCalledWith("a"); - - evaluator.evaluate.mockClear(); - evaluator.stringConvert.mockClear(); - - expect(operator.evaluate(evaluator, [haystackbc, "b"])).toBe(true); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, haystackbc); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, "b"); - expect(evaluator.stringConvert).toHaveBeenCalledTimes(1); - expect(evaluator.stringConvert).toHaveBeenCalledWith("b"); - - evaluator.evaluate.mockClear(); - evaluator.stringConvert.mockClear(); - - expect(operator.evaluate(evaluator, [haystackbc, "c"])).toBe(true); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, haystackbc); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, "c"); - expect(evaluator.stringConvert).toHaveBeenCalledTimes(1); - expect(evaluator.stringConvert).toHaveBeenCalledWith("c"); - - evaluator.evaluate.mockClear(); - evaluator.stringConvert.mockClear(); - - expect(operator.evaluate(evaluator, [haystackbc, 0])).toBe(true); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, haystackbc); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, 0); - expect(evaluator.stringConvert).toHaveBeenCalledTimes(1); - expect(evaluator.stringConvert).toHaveBeenCalledWith(0); - - evaluator.evaluate.mockClear(); - evaluator.stringConvert.mockClear(); - }); - }); -}); - -/* - assertTrue((Boolean) operator.evaluate(evaluator, listOf(haystackbc, "b"))); - verify(evaluator, times(2)).evaluate(any()); - verify(evaluator, times(1)).evaluate(haystackbc); - verify(evaluator, times(1)).stringConvert(any()); - verify(evaluator, times(1)).stringConvert("b"); - verify(evaluator, times(1)).evaluate("b"); - Mockito.clearInvocations(evaluator); - - assertTrue((Boolean) operator.evaluate(evaluator, listOf(haystackbc, "c"))); - verify(evaluator, times(2)).evaluate(any()); - verify(evaluator, times(1)).evaluate(haystackbc); - verify(evaluator, times(1)).stringConvert(any()); - verify(evaluator, times(1)).stringConvert("c"); - verify(evaluator, times(1)).evaluate("c"); - Mockito.clearInvocations(evaluator); - */ diff --git a/src/__tests__/jsonexpr/operators/lt.test.js b/src/__tests__/jsonexpr/operators/lt.test.js deleted file mode 100644 index fac955e..0000000 --- a/src/__tests__/jsonexpr/operators/lt.test.js +++ /dev/null @@ -1,50 +0,0 @@ -import { mockEvaluator } from "./evaluator"; -import { LessThanOperator } from "../../../jsonexpr/operators/lt"; - -describe("LessThanOperator", () => { - const operator = new LessThanOperator(); - - describe("evaluate", () => { - const evaluator = mockEvaluator(); - - it("should return true when left-side argument is less and comparable", () => { - expect(operator.evaluate(evaluator, [0, 0])).toBe(false); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, 0); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, 0); - expect(evaluator.compare).toHaveBeenCalledTimes(1); - expect(evaluator.compare).toHaveBeenCalledWith(0, 0); - - evaluator.evaluate.mockClear(); - evaluator.compare.mockClear(); - - expect(operator.evaluate(evaluator, [1, 0])).toBe(false); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, 1); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, 0); - expect(evaluator.compare).toHaveBeenCalledTimes(1); - expect(evaluator.compare).toHaveBeenCalledWith(1, 0); - - evaluator.evaluate.mockClear(); - evaluator.compare.mockClear(); - - expect(operator.evaluate(evaluator, [0, 1])).toBe(true); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, 0); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, 1); - expect(evaluator.compare).toHaveBeenCalledTimes(1); - expect(evaluator.compare).toHaveBeenCalledWith(0, 1); - - evaluator.evaluate.mockClear(); - evaluator.compare.mockClear(); - - expect(operator.evaluate(evaluator, [null, null])).toBe(null); - expect(evaluator.evaluate).toHaveBeenCalledTimes(1); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, null); - expect(evaluator.compare).toHaveBeenCalledTimes(0); - - evaluator.evaluate.mockClear(); - evaluator.compare.mockClear(); - }); - }); -}); diff --git a/src/__tests__/jsonexpr/operators/lte.test.js b/src/__tests__/jsonexpr/operators/lte.test.js deleted file mode 100644 index bb59afc..0000000 --- a/src/__tests__/jsonexpr/operators/lte.test.js +++ /dev/null @@ -1,50 +0,0 @@ -import { mockEvaluator } from "./evaluator"; -import { LessThanOrEqualOperator } from "../../../jsonexpr/operators/lte"; - -describe("LessThanOrEqualOperator", () => { - const operator = new LessThanOrEqualOperator(); - - describe("evaluate", () => { - const evaluator = mockEvaluator(); - - it("should return true when left-side argument is less or equal and comparable", () => { - expect(operator.evaluate(evaluator, [0, 0])).toBe(true); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, 0); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, 0); - expect(evaluator.compare).toHaveBeenCalledTimes(1); - expect(evaluator.compare).toHaveBeenCalledWith(0, 0); - - evaluator.evaluate.mockClear(); - evaluator.compare.mockClear(); - - expect(operator.evaluate(evaluator, [1, 0])).toBe(false); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, 1); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, 0); - expect(evaluator.compare).toHaveBeenCalledTimes(1); - expect(evaluator.compare).toHaveBeenCalledWith(1, 0); - - evaluator.evaluate.mockClear(); - evaluator.compare.mockClear(); - - expect(operator.evaluate(evaluator, [0, 1])).toBe(true); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, 0); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, 1); - expect(evaluator.compare).toHaveBeenCalledTimes(1); - expect(evaluator.compare).toHaveBeenCalledWith(0, 1); - - evaluator.evaluate.mockClear(); - evaluator.compare.mockClear(); - - expect(operator.evaluate(evaluator, [null, null])).toBe(null); - expect(evaluator.evaluate).toHaveBeenCalledTimes(1); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, null); - expect(evaluator.compare).toHaveBeenCalledTimes(0); - - evaluator.evaluate.mockClear(); - evaluator.compare.mockClear(); - }); - }); -}); diff --git a/src/__tests__/jsonexpr/operators/match.test.js b/src/__tests__/jsonexpr/operators/match.test.js deleted file mode 100644 index 4783aa1..0000000 --- a/src/__tests__/jsonexpr/operators/match.test.js +++ /dev/null @@ -1,35 +0,0 @@ -import { mockEvaluator } from "./evaluator"; -import { MatchOperator } from "../../../jsonexpr/operators/match"; - -describe("MatchOperator", () => { - const operator = new MatchOperator(); - - describe("evaluate", () => { - const evaluator = mockEvaluator(); - - it("", () => { - expect(operator.evaluate(evaluator, ["abcdefghijk", ""])).toBe(true); - expect(operator.evaluate(evaluator, ["abcdefghijk", "abc"])).toBe(true); - expect(operator.evaluate(evaluator, ["abcdefghijk", "ijk"])).toBe(true); - expect(operator.evaluate(evaluator, ["abcdefghijk", "^abc"])).toBe(true); - expect(operator.evaluate(evaluator, ["abcdefghijk", "ijk$"])).toBe(true); - expect(operator.evaluate(evaluator, ["abcdefghijk", "def"])).toBe(true); - expect(operator.evaluate(evaluator, ["abcdefghijk", "b.*j"])).toBe(true); - expect(operator.evaluate(evaluator, ["abcdefghijk", "xyz"])).toBe(false); - - expect(operator.evaluate(evaluator, [null, "abc"])).toBe(null); - expect(operator.evaluate(evaluator, ["abcdefghijk", null])).toBe(null); - }); - }); -}); - -/* - assertTrue((Boolean) operator.evaluate(evaluator, listOf("abcdefghijk", "abc"))); - assertTrue((Boolean) operator.evaluate(evaluator, listOf("abcdefghijk", "ijk"))); - assertTrue((Boolean) operator.evaluate(evaluator, listOf("abcdefghijk", "^abc"))); - assertTrue((Boolean) operator.evaluate(evaluator, listOf(",l5abcdefghijk", "ijk$"))); - assertTrue((Boolean) operator.evaluate(evaluator, listOf("abcdefghijk", "def"))); - assertTrue((Boolean) operator.evaluate(evaluator, listOf("abcdefghijk", "b.*j"))); - assertFalse((Boolean) operator.evaluate(evaluator, listOf("abcdefghijk", "xyz"))); - - */ diff --git a/src/__tests__/jsonexpr/operators/not.test.js b/src/__tests__/jsonexpr/operators/not.test.js deleted file mode 100644 index a848892..0000000 --- a/src/__tests__/jsonexpr/operators/not.test.js +++ /dev/null @@ -1,37 +0,0 @@ -import { mockEvaluator } from "./evaluator"; -import { NotOperator } from "../../../jsonexpr/operators/not"; - -describe("NotOperator", () => { - const operator = new NotOperator(); - - describe("evaluate", () => { - const evaluator = mockEvaluator(); - - it("should return true if argument is falsy", () => { - expect(operator.evaluate(evaluator, false)).toBe(true); - - expect(evaluator.evaluate).toHaveBeenCalledTimes(1); - expect(evaluator.evaluate).toHaveBeenCalledWith(false); - expect(evaluator.booleanConvert).toHaveBeenCalledTimes(1); - expect(evaluator.booleanConvert).toHaveBeenCalledWith(false); - }); - - it("should return false if argument is truthy", () => { - expect(operator.evaluate(evaluator, true)).toBe(false); - - expect(evaluator.evaluate).toHaveBeenCalledTimes(1); - expect(evaluator.evaluate).toHaveBeenCalledWith(true); - expect(evaluator.booleanConvert).toHaveBeenCalledTimes(1); - expect(evaluator.booleanConvert).toHaveBeenCalledWith(true); - }); - - it("should return true if argument is null", () => { - expect(operator.evaluate(evaluator, null)).toBe(true); - - expect(evaluator.evaluate).toHaveBeenCalledTimes(1); - expect(evaluator.evaluate).toHaveBeenCalledWith(null); - expect(evaluator.booleanConvert).toHaveBeenCalledTimes(1); - expect(evaluator.booleanConvert).toHaveBeenCalledWith(null); - }); - }); -}); diff --git a/src/__tests__/jsonexpr/operators/null.test.js b/src/__tests__/jsonexpr/operators/null.test.js deleted file mode 100644 index e464821..0000000 --- a/src/__tests__/jsonexpr/operators/null.test.js +++ /dev/null @@ -1,53 +0,0 @@ -import { mockEvaluator } from "./evaluator"; -import { NullOperator } from "../../../jsonexpr/operators/null"; - -describe("NullOperator", () => { - const operator = new NullOperator(); - - describe("evaluate", () => { - const evaluator = mockEvaluator(); - - it("should return true if argument is null", () => { - expect(operator.evaluate(evaluator, null)).toBe(true); - - expect(evaluator.evaluate).toHaveBeenCalledTimes(1); - expect(evaluator.evaluate).toHaveBeenCalledWith(null); - expect(evaluator.booleanConvert).not.toHaveBeenCalled(); - }); - - it("should return true if argument is not null", () => { - expect(operator.evaluate(evaluator, true)).toBe(false); - - expect(evaluator.evaluate).toHaveBeenCalledTimes(1); - expect(evaluator.evaluate).toHaveBeenCalledWith(true); - - expect(operator.evaluate(evaluator, false)).toBe(false); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(2, false); - - expect(operator.evaluate(evaluator, 0)).toBe(false); - expect(evaluator.evaluate).toHaveBeenCalledTimes(3); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(3, 0); - }); - }); -}); - -/* - @Test - void testNull() { - assertTrue((Boolean) operator.evaluate(evaluator, null)); - verify(evaluator, times(1)).evaluate(null); - } - - @Test - void testNotNull() { - assertFalse((Boolean) operator.evaluate(evaluator, true)); - verify(evaluator, times(1)).evaluate(true); - - assertFalse((Boolean) operator.evaluate(evaluator, false)); - verify(evaluator, times(1)).evaluate(false); - - assertFalse((Boolean) operator.evaluate(evaluator, 0)); - verify(evaluator, times(1)).evaluate(0); - } - */ diff --git a/src/__tests__/jsonexpr/operators/or.test.js b/src/__tests__/jsonexpr/operators/or.test.js deleted file mode 100644 index 566a223..0000000 --- a/src/__tests__/jsonexpr/operators/or.test.js +++ /dev/null @@ -1,52 +0,0 @@ -import { mockEvaluator } from "./evaluator"; -import { OrCombinator } from "../../../jsonexpr/operators/or"; - -describe("OrCombinator", () => { - const combinator = new OrCombinator(); - - describe("evaluate", () => { - const evaluator = mockEvaluator(); - - it("should return true if any argument evaluates to true", () => { - expect(combinator.evaluate(evaluator, [true])).toBe(true); - expect(evaluator.evaluate).toHaveBeenCalledTimes(1); - expect(evaluator.evaluate).toHaveBeenCalledWith(true); - expect(evaluator.booleanConvert).toHaveBeenCalledTimes(1); - expect(evaluator.booleanConvert).toHaveBeenCalledWith(true); - }); - - it("should return false if all arguments evaluate to false", () => { - expect(combinator.evaluate(evaluator, [false])).toBe(false); - expect(evaluator.evaluate).toHaveBeenCalledTimes(1); - expect(evaluator.evaluate).toHaveBeenCalledWith(false); - expect(evaluator.booleanConvert).toHaveBeenCalledTimes(1); - expect(evaluator.booleanConvert).toHaveBeenCalledWith(false); - }); - - it("should return false if all arguments evaluates to null", () => { - expect(combinator.evaluate(evaluator, [null])).toBe(false); - expect(evaluator.evaluate).toHaveBeenCalledTimes(1); - expect(evaluator.evaluate).toHaveBeenCalledWith(null); - expect(evaluator.booleanConvert).toHaveBeenCalledTimes(1); - expect(evaluator.booleanConvert).toHaveBeenCalledWith(null); - }); - - it("should short-circuit and not evaluate unnecessary expressions", () => { - expect(combinator.evaluate(evaluator, [true, false, true])).toBe(true); - expect(evaluator.evaluate).toHaveBeenCalledTimes(1); - expect(evaluator.booleanConvert).toHaveBeenCalledTimes(1); - expect(evaluator.evaluate).toHaveBeenNthCalledWith(1, true); - expect(evaluator.booleanConvert).toHaveBeenNthCalledWith(1, true); - }); - - it("should combine multiple arguments", () => { - expect(combinator.evaluate(evaluator, [true, true])).toBe(true); - expect(combinator.evaluate(evaluator, [true, true, true])).toBe(true); - - expect(combinator.evaluate(evaluator, [true, false])).toBe(true); - expect(combinator.evaluate(evaluator, [false, true])).toBe(true); - expect(combinator.evaluate(evaluator, [false, false])).toBe(false); - expect(combinator.evaluate(evaluator, [false, false, false])).toBe(false); - }); - }); -}); diff --git a/src/__tests__/jsonexpr/operators/semver_eq.test.js b/src/__tests__/jsonexpr/operators/semver_eq.test.js deleted file mode 100644 index 187747b..0000000 --- a/src/__tests__/jsonexpr/operators/semver_eq.test.js +++ /dev/null @@ -1,38 +0,0 @@ -import { mockEvaluator } from "./evaluator"; -import { SemverEqualsOperator } from "../../../jsonexpr/operators/semver_eq"; - -describe("SemverEqualsOperator", () => { - const operator = new SemverEqualsOperator(); - - describe("evaluate", () => { - const evaluator = mockEvaluator(); - - afterEach(() => { - evaluator.evaluate.mockClear(); - evaluator.versionCompare.mockClear(); - }); - - it("should return true when versions are equal", () => { - expect(operator.evaluate(evaluator, ["1.0.0", "1.0.0"])).toBe(true); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.versionCompare).toHaveBeenCalledWith("1.0.0", "1.0.0"); - }); - - it("should return false when versions are not equal", () => { - expect(operator.evaluate(evaluator, ["1.0.0", "2.0.0"])).toBe(false); - expect(evaluator.versionCompare).toHaveBeenCalledWith("1.0.0", "2.0.0"); - }); - - it("should return null when lhs is null", () => { - expect(operator.evaluate(evaluator, [null, "1.0.0"])).toBe(null); - expect(evaluator.evaluate).toHaveBeenCalledTimes(1); - expect(evaluator.versionCompare).not.toHaveBeenCalled(); - }); - - it("should return null when rhs is null", () => { - expect(operator.evaluate(evaluator, ["1.0.0", null])).toBe(null); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.versionCompare).not.toHaveBeenCalled(); - }); - }); -}); diff --git a/src/__tests__/jsonexpr/operators/semver_gt.test.js b/src/__tests__/jsonexpr/operators/semver_gt.test.js deleted file mode 100644 index bb63d6f..0000000 --- a/src/__tests__/jsonexpr/operators/semver_gt.test.js +++ /dev/null @@ -1,42 +0,0 @@ -import { mockEvaluator } from "./evaluator"; -import { SemverGreaterThanOperator } from "../../../jsonexpr/operators/semver_gt"; - -describe("SemverGreaterThanOperator", () => { - const operator = new SemverGreaterThanOperator(); - - describe("evaluate", () => { - const evaluator = mockEvaluator(); - - afterEach(() => { - evaluator.evaluate.mockClear(); - evaluator.versionCompare.mockClear(); - }); - - it("should return true when left version is greater", () => { - expect(operator.evaluate(evaluator, ["2.0.0", "1.0.0"])).toBe(true); - expect(evaluator.versionCompare).toHaveBeenCalledWith("2.0.0", "1.0.0"); - }); - - it("should return false when versions are equal", () => { - expect(operator.evaluate(evaluator, ["1.0.0", "1.0.0"])).toBe(false); - expect(evaluator.versionCompare).toHaveBeenCalledWith("1.0.0", "1.0.0"); - }); - - it("should return false when left version is less", () => { - expect(operator.evaluate(evaluator, ["1.0.0", "2.0.0"])).toBe(false); - expect(evaluator.versionCompare).toHaveBeenCalledWith("1.0.0", "2.0.0"); - }); - - it("should return null when lhs is null", () => { - expect(operator.evaluate(evaluator, [null, "1.0.0"])).toBe(null); - expect(evaluator.evaluate).toHaveBeenCalledTimes(1); - expect(evaluator.versionCompare).not.toHaveBeenCalled(); - }); - - it("should return null when rhs is null", () => { - expect(operator.evaluate(evaluator, ["1.0.0", null])).toBe(null); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.versionCompare).not.toHaveBeenCalled(); - }); - }); -}); diff --git a/src/__tests__/jsonexpr/operators/semver_gte.test.js b/src/__tests__/jsonexpr/operators/semver_gte.test.js deleted file mode 100644 index 38e6e84..0000000 --- a/src/__tests__/jsonexpr/operators/semver_gte.test.js +++ /dev/null @@ -1,42 +0,0 @@ -import { mockEvaluator } from "./evaluator"; -import { SemverGreaterThanOrEqualOperator } from "../../../jsonexpr/operators/semver_gte"; - -describe("SemverGreaterThanOrEqualOperator", () => { - const operator = new SemverGreaterThanOrEqualOperator(); - - describe("evaluate", () => { - const evaluator = mockEvaluator(); - - afterEach(() => { - evaluator.evaluate.mockClear(); - evaluator.versionCompare.mockClear(); - }); - - it("should return true when left version is greater", () => { - expect(operator.evaluate(evaluator, ["2.0.0", "1.0.0"])).toBe(true); - expect(evaluator.versionCompare).toHaveBeenCalledWith("2.0.0", "1.0.0"); - }); - - it("should return true when versions are equal", () => { - expect(operator.evaluate(evaluator, ["1.0.0", "1.0.0"])).toBe(true); - expect(evaluator.versionCompare).toHaveBeenCalledWith("1.0.0", "1.0.0"); - }); - - it("should return false when left version is less", () => { - expect(operator.evaluate(evaluator, ["1.0.0", "2.0.0"])).toBe(false); - expect(evaluator.versionCompare).toHaveBeenCalledWith("1.0.0", "2.0.0"); - }); - - it("should return null when lhs is null", () => { - expect(operator.evaluate(evaluator, [null, "1.0.0"])).toBe(null); - expect(evaluator.evaluate).toHaveBeenCalledTimes(1); - expect(evaluator.versionCompare).not.toHaveBeenCalled(); - }); - - it("should return null when rhs is null", () => { - expect(operator.evaluate(evaluator, ["1.0.0", null])).toBe(null); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.versionCompare).not.toHaveBeenCalled(); - }); - }); -}); diff --git a/src/__tests__/jsonexpr/operators/semver_lt.test.js b/src/__tests__/jsonexpr/operators/semver_lt.test.js deleted file mode 100644 index 410619f..0000000 --- a/src/__tests__/jsonexpr/operators/semver_lt.test.js +++ /dev/null @@ -1,42 +0,0 @@ -import { mockEvaluator } from "./evaluator"; -import { SemverLessThanOperator } from "../../../jsonexpr/operators/semver_lt"; - -describe("SemverLessThanOperator", () => { - const operator = new SemverLessThanOperator(); - - describe("evaluate", () => { - const evaluator = mockEvaluator(); - - afterEach(() => { - evaluator.evaluate.mockClear(); - evaluator.versionCompare.mockClear(); - }); - - it("should return true when left version is less", () => { - expect(operator.evaluate(evaluator, ["1.0.0", "2.0.0"])).toBe(true); - expect(evaluator.versionCompare).toHaveBeenCalledWith("1.0.0", "2.0.0"); - }); - - it("should return false when versions are equal", () => { - expect(operator.evaluate(evaluator, ["1.0.0", "1.0.0"])).toBe(false); - expect(evaluator.versionCompare).toHaveBeenCalledWith("1.0.0", "1.0.0"); - }); - - it("should return false when left version is greater", () => { - expect(operator.evaluate(evaluator, ["2.0.0", "1.0.0"])).toBe(false); - expect(evaluator.versionCompare).toHaveBeenCalledWith("2.0.0", "1.0.0"); - }); - - it("should return null when lhs is null", () => { - expect(operator.evaluate(evaluator, [null, "1.0.0"])).toBe(null); - expect(evaluator.evaluate).toHaveBeenCalledTimes(1); - expect(evaluator.versionCompare).not.toHaveBeenCalled(); - }); - - it("should return null when rhs is null", () => { - expect(operator.evaluate(evaluator, ["1.0.0", null])).toBe(null); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.versionCompare).not.toHaveBeenCalled(); - }); - }); -}); diff --git a/src/__tests__/jsonexpr/operators/semver_lte.test.js b/src/__tests__/jsonexpr/operators/semver_lte.test.js deleted file mode 100644 index b4a870f..0000000 --- a/src/__tests__/jsonexpr/operators/semver_lte.test.js +++ /dev/null @@ -1,42 +0,0 @@ -import { mockEvaluator } from "./evaluator"; -import { SemverLessThanOrEqualOperator } from "../../../jsonexpr/operators/semver_lte"; - -describe("SemverLessThanOrEqualOperator", () => { - const operator = new SemverLessThanOrEqualOperator(); - - describe("evaluate", () => { - const evaluator = mockEvaluator(); - - afterEach(() => { - evaluator.evaluate.mockClear(); - evaluator.versionCompare.mockClear(); - }); - - it("should return true when left version is less", () => { - expect(operator.evaluate(evaluator, ["1.0.0", "2.0.0"])).toBe(true); - expect(evaluator.versionCompare).toHaveBeenCalledWith("1.0.0", "2.0.0"); - }); - - it("should return true when versions are equal", () => { - expect(operator.evaluate(evaluator, ["1.0.0", "1.0.0"])).toBe(true); - expect(evaluator.versionCompare).toHaveBeenCalledWith("1.0.0", "1.0.0"); - }); - - it("should return false when left version is greater", () => { - expect(operator.evaluate(evaluator, ["2.0.0", "1.0.0"])).toBe(false); - expect(evaluator.versionCompare).toHaveBeenCalledWith("2.0.0", "1.0.0"); - }); - - it("should return null when lhs is null", () => { - expect(operator.evaluate(evaluator, [null, "1.0.0"])).toBe(null); - expect(evaluator.evaluate).toHaveBeenCalledTimes(1); - expect(evaluator.versionCompare).not.toHaveBeenCalled(); - }); - - it("should return null when rhs is null", () => { - expect(operator.evaluate(evaluator, ["1.0.0", null])).toBe(null); - expect(evaluator.evaluate).toHaveBeenCalledTimes(2); - expect(evaluator.versionCompare).not.toHaveBeenCalled(); - }); - }); -}); diff --git a/src/__tests__/jsonexpr/operators/value.test.js b/src/__tests__/jsonexpr/operators/value.test.js deleted file mode 100644 index d676acd..0000000 --- a/src/__tests__/jsonexpr/operators/value.test.js +++ /dev/null @@ -1,23 +0,0 @@ -import { mockEvaluator } from "./evaluator"; -import { ValueOperator } from "../../../jsonexpr/operators/value"; - -describe("ValueOperator", () => { - const operator = new ValueOperator(); - - describe("evaluate", () => { - const evaluator = mockEvaluator(); - - it("should not call evaluator evaluate", () => { - expect(operator.evaluate(evaluator, 0)).toBe(0); - expect(operator.evaluate(evaluator, 1)).toBe(1); - expect(operator.evaluate(evaluator, true)).toBe(true); - expect(operator.evaluate(evaluator, false)).toBe(false); - expect(operator.evaluate(evaluator, "")).toBe(""); - expect(operator.evaluate(evaluator, null)).toBe(null); - expect(operator.evaluate(evaluator, {})).toEqual({}); - expect(operator.evaluate(evaluator, [])).toEqual([]); - - expect(evaluator.evaluate).not.toHaveBeenCalled(); - }); - }); -}); diff --git a/src/__tests__/jsonexpr/operators/var.test.js b/src/__tests__/jsonexpr/operators/var.test.js deleted file mode 100644 index ac00c64..0000000 --- a/src/__tests__/jsonexpr/operators/var.test.js +++ /dev/null @@ -1,18 +0,0 @@ -import { mockEvaluator } from "./evaluator"; -import { VarOperator } from "../../../jsonexpr/operators/var"; - -describe("VarOperator", () => { - const operator = new VarOperator(); - - describe("evaluate", () => { - const evaluator = mockEvaluator(); - - it("should call evaluator extract", () => { - expect(operator.evaluate(evaluator, "a/b/c")).toBe("abc"); - - expect(evaluator.extractVar).toHaveBeenCalledTimes(1); - expect(evaluator.extractVar).toHaveBeenCalledWith("a/b/c"); - expect(evaluator.evaluate).not.toHaveBeenCalled(); - }); - }); -}); diff --git a/src/__tests__/matcher.test.js b/src/__tests__/matcher.test.js deleted file mode 100644 index 05c345c..0000000 --- a/src/__tests__/matcher.test.js +++ /dev/null @@ -1,49 +0,0 @@ -import { AudienceMatcher } from "../matcher"; - -describe("AudienceMatcher", () => { - const matcher = new AudienceMatcher(); - - it("should return null on empty audience", () => { - expect(matcher.evaluate("", null)).toBe(null); - expect(matcher.evaluate("{}", null)).toBe(null); - expect(matcher.evaluate("null", null)).toBe(null); - }); - - it("should return null if filter not object or array", () => { - expect(matcher.evaluate('{"filter":null}', null)).toBe(null); - expect(matcher.evaluate('{"filter":false}', null)).toBe(null); - expect(matcher.evaluate('{"filter":5}', null)).toBe(null); - expect(matcher.evaluate('{"filter":"a"}', null)).toBe(null); - }); - - it("should return boolean", () => { - expect(matcher.evaluate('{"filter":[{"value":5}]}', null)).toBe(true); - expect(matcher.evaluate('{"filter":[{"value":true}]}', null)).toBe(true); - expect(matcher.evaluate('{"filter":[{"value":1}]}', null)).toBe(true); - expect(matcher.evaluate('{"filter":[{"value":null}]}', null)).toBe(false); - expect(matcher.evaluate('{"filter":[{"value":0}]}', null)).toBe(false); - - expect(matcher.evaluate('{"filter":[{"not":{"var":"returning"}}]}', { returning: true })).toBe(false); - expect(matcher.evaluate('{"filter":[{"not":{"var":"returning"}}]}', { returning: false })).toBe(true); - }); -}); - -/* - - @Test - void evaluateReturnsNullIfFilterNotMapOrList() { - assertNull(matcher.evaluate("{\"filter\":5}", null)); - } - - @Test - void evaluateReturnsBoolean() { - assertTrue(matcher.evaluate("{\"filter\":[{\"value\":5}]}", null)); - assertTrue(matcher.evaluate("{\"filter\":[{\"value\":true}]}", null)); - assertTrue(matcher.evaluate("{\"filter\":[{\"value\":1}]}", null)); - assertFalse(matcher.evaluate("{\"filter\":[{\"value\":null}]}", null)); - assertFalse(matcher.evaluate("{\"filter\":[{\"value\":0}]}", null)); - - assertFalse(matcher.evaluate("{\"filter\":[{\"not\":{\"var\":\"returning\"}}]}", mapOf("returning", true))); - assertTrue(matcher.evaluate("{\"filter\":[{\"not\":{\"var\":\"returning\"}}]}", mapOf("returning", false))); - } - */ diff --git a/src/__tests__/md5.test.js b/src/__tests__/md5.test.js deleted file mode 100644 index 6086c21..0000000 --- a/src/__tests__/md5.test.js +++ /dev/null @@ -1,34 +0,0 @@ -import { md5 } from "../md5"; -import { base64UrlNoPadding, stringToUint8Array } from "../utils"; - -describe("md5()", () => { - it("should match known hashes", (done) => { - const testCases = [ - ["", "1B2M2Y8AsgTpgAmY7PhCfg"], - [" ", "chXunH2dwinSkhpA6JnsXw"], - ["t", "41jvpIn1gGLxDdcxa2Vkng"], - ["te", "Vp73JkK-D63XEdakaNaO4Q"], - ["tes", "KLZi2IO212_Zbk3cXpungA"], - ["test", "CY9rzUYh03PK3k6DJie09g"], - ["testy", "K5I_V6RgP8c6sYKz-TVn8g"], - ["testy1", "8fT8xGipOhPkZ2DncKU-1A"], - ["testy12", "YqRAtOz000gIu61ErEH18A"], - ["testy123", "pfV2H07L6WvdqlY0zHuYIw"], - ["special characters açb↓c", "4PIrO7lKtTxOcj2eMYlG7A"], - ["The quick brown fox jumps over the lazy dog", "nhB9nTcrtoJr2B01QqQZ1g"], - ["The quick brown fox jumps over the lazy dog and eats a pie", "iM-8ECRrLUQzixl436y96A"], - [ - "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "24m7XOq4f5wPzCqzbBicLA", - ], - ]; - - testCases.forEach((testCase) => { - const bytes = stringToUint8Array(testCase[0]); - const hash = md5(bytes.buffer); - expect(base64UrlNoPadding(hash)).toEqual(testCase[1]); - }); - - done(); - }); -}); diff --git a/src/__tests__/murmur3_32.test.js b/src/__tests__/murmur3_32.test.js deleted file mode 100644 index 6b5a279..0000000 --- a/src/__tests__/murmur3_32.test.js +++ /dev/null @@ -1,52 +0,0 @@ -import { stringToUint8Array } from "../utils"; -import { murmur3_32 } from "../murmur3_32"; - -describe("murmur3_32()", () => { - const testCases = [ - ["", 0x00000000, 0x00000000], - [" ", 0x00000000, 0x7ef49b98], - ["t", 0x00000000, 0xca87df4d], - ["te", 0x00000000, 0xedb8ee1b], - ["tes", 0x00000000, 0x0bb90e5a], - ["test", 0x00000000, 0xba6bd213], - ["testy", 0x00000000, 0x44af8342], - ["testy1", 0x00000000, 0x8a1a243a], - ["testy12", 0x00000000, 0x845461b9], - ["testy123", 0x00000000, 0x47628ac4], - ["special characters açb↓c", 0x00000000, 0xbe83b140], - ["The quick brown fox jumps over the lazy dog", 0x00000000, 0x2e4ff723], - ["", 0xdeadbeef, 0x0de5c6a9], - [" ", 0xdeadbeef, 0x25acce43], - ["t", 0xdeadbeef, 0x3b15dcf8], - ["te", 0xdeadbeef, 0xac981332], - ["tes", 0xdeadbeef, 0xc1c78dda], - ["test", 0xdeadbeef, 0xaa22d41a], - ["testy", 0xdeadbeef, 0x84f5f623], - ["testy1", 0xdeadbeef, 0x09ed28e9], - ["testy12", 0xdeadbeef, 0x22467835], - ["testy123", 0xdeadbeef, 0xd633060d], - ["special characters açb↓c", 0xdeadbeef, 0xf7fdd8a2], - ["The quick brown fox jumps over the lazy dog", 0xdeadbeef, 0x3a7b3f4d], - ["", 0x00000001, 0x514e28b7], - [" ", 0x00000001, 0x4f0f7132], - ["t", 0x00000001, 0x5db1831e], - ["te", 0x00000001, 0xd248bb2e], - ["tes", 0x00000001, 0xd432eb74], - ["test", 0x00000001, 0x99c02ae2], - ["testy", 0x00000001, 0xc5b2dc1e], - ["testy1", 0x00000001, 0x33925ceb], - ["testy12", 0x00000001, 0xd92c9f23], - ["testy123", 0x00000001, 0x3bc1712d], - ["special characters açb↓c", 0x00000001, 0x293327b5], - ["The quick brown fox jumps over the lazy dog", 0x00000001, 0x78e69e27], - ]; - - it("should match known hashes", (done) => { - testCases.forEach((testCase) => { - const bytes = stringToUint8Array(testCase[0]); - expect(murmur3_32(bytes.buffer, testCase[1])).toBe(testCase[2]); - }); - - done(); - }); -}); diff --git a/src/__tests__/provider.test.js b/src/__tests__/provider.test.js deleted file mode 100644 index ed15180..0000000 --- a/src/__tests__/provider.test.js +++ /dev/null @@ -1,51 +0,0 @@ -import Client from "../client"; -import SDK from "../sdk"; -import { ContextDataProvider } from "../provider"; - -jest.mock("../client"); -jest.mock("../sdk"); - -describe("ContextDataProvider", () => { - const client = new Client(); - const sdk = new SDK(); - - sdk.getClient.mockReturnValue(client); - - describe("getContextData()", () => { - it("should call client getContext", async () => { - const provider = new ContextDataProvider(); - - const data = {}; - client.getContext.mockReturnValue(Promise.resolve(data)); - - const result = provider.getContextData(sdk); - - expect(result).toBeInstanceOf(Promise); - expect(client.getContext).toHaveBeenCalledTimes(1); - expect(client.getContext).toHaveBeenCalledWith(undefined); - - result.then((resp) => { - expect(resp).toBe(data); - }); - }); - - it("should pass through options", async () => { - const provider = new ContextDataProvider(); - - const data = {}; - client.getContext.mockReturnValue(Promise.resolve(data)); - - const result = provider.getContextData(sdk, { timeout: 1234 }); - - expect(result).toBeInstanceOf(Promise); - expect(client.getContext).toHaveBeenCalledTimes(1); - expect(client.getContext).toHaveBeenCalledWith({ - timeout: 1234, - }); - - result.then((resp) => { - expect(resp).toBe(data); - }); - }); - }); -}); diff --git a/src/__tests__/publisher.test.js b/src/__tests__/publisher.test.js deleted file mode 100644 index 4d59dbe..0000000 --- a/src/__tests__/publisher.test.js +++ /dev/null @@ -1,56 +0,0 @@ -import Client from "../client"; -import SDK from "../sdk"; -import Context from "../context"; -import { ContextPublisher } from "../publisher"; - -jest.mock("../client"); -jest.mock("../sdk"); -jest.mock("../context"); - -describe("ContextPublisher", () => { - const client = new Client(); - const sdk = new SDK(); - const context = new Context(); - - sdk.getClient.mockReturnValue(client); - - describe("publish()", () => { - it("should call client publish", async () => { - const publisher = new ContextPublisher(); - - const data = { ok: true }; - client.publish.mockReturnValue(Promise.resolve(data)); - - const request = { test: 1 }; - const result = publisher.publish(request, sdk, context); - - expect(result).toBeInstanceOf(Promise); - expect(client.publish).toHaveBeenCalledTimes(1); - expect(client.publish).toHaveBeenCalledWith(request, undefined); - - result.then((resp) => { - expect(resp).toBe(data); - }); - }); - - it("should pass through options", async () => { - const publisher = new ContextPublisher(); - - const data = { ok: true }; - client.publish.mockReturnValue(Promise.resolve(data)); - - const request = { test: 1 }; - const result = publisher.publish(request, sdk, context, { timeout: 1234 }); - - expect(result).toBeInstanceOf(Promise); - expect(client.publish).toHaveBeenCalledTimes(1); - expect(client.publish).toHaveBeenCalledWith(request, { - timeout: 1234, - }); - - result.then((resp) => { - expect(resp).toBe(data); - }); - }); - }); -}); diff --git a/src/__tests__/sdk.test.js b/src/__tests__/sdk.test.js deleted file mode 100644 index 3dc75de..0000000 --- a/src/__tests__/sdk.test.js +++ /dev/null @@ -1,479 +0,0 @@ -import Client from "../client"; -import SDK from "../sdk"; -import Context from "../context"; -import { ContextPublisher } from "../publisher"; -import { ContextDataProvider } from "../provider"; - -jest.mock("../client"); -jest.mock("../context"); -jest.mock("../publisher"); -jest.mock("../provider"); - -describe("SDK", () => { - const contextParams = { - units: { - session_id: "ab", - }, - }; - - const testEventLogger = jest.fn(); - const testContextDataProvider = new ContextDataProvider(); - const testContextPublisher = new ContextPublisher(); - - const sdkOptions = { - agent: "javascript-sdk", - apiKey: "apikey", - application: "website", - endpoint: "localhost:8080", - environment: "test", - eventLogger: testEventLogger, - publisher: testContextPublisher, - provider: testContextDataProvider, - timeout: 1000, - }; - - describe("constructor()", () => { - it("should try to create a client with no options", (done) => { - const sdk = new SDK(); - - expect(sdk).toBeInstanceOf(SDK); - expect(sdk.getEventLogger()).toBe(SDK.defaultEventLogger); - expect(Client).toHaveBeenCalledTimes(1); - expect(Client).toHaveBeenCalledWith({ - agent: "absmartly-javascript-sdk", - }); - - expect(ContextDataProvider).toHaveBeenCalledTimes(1); - expect(ContextDataProvider).toHaveBeenCalledWith(); - - expect(ContextPublisher).toHaveBeenCalledTimes(1); - expect(ContextPublisher).toHaveBeenCalledWith(); - - expect(sdk.getClient()).toBeInstanceOf(Client); - expect(sdk.getContextDataProvider()).toBeInstanceOf(ContextDataProvider); - expect(sdk.getContextPublisher()).toBeInstanceOf(ContextPublisher); - - done(); - }); - - it("should create a client with specified options", (done) => { - const sdk = new SDK(sdkOptions); - - expect(sdk).toBeInstanceOf(SDK); - expect(sdk.getEventLogger()).toBe(testEventLogger); - expect(sdk.getContextDataProvider()).toBe(testContextDataProvider); - expect(sdk.getContextPublisher()).toBe(testContextPublisher); - expect(Client).toHaveBeenCalledTimes(1); - expect(Client).toHaveBeenCalledWith({ - agent: "javascript-sdk", - apiKey: "apikey", - application: "website", - endpoint: "localhost:8080", - environment: "test", - timeout: 1000, - }); - - expect(sdk.getClient()).toBeInstanceOf(Client); - expect(sdk.getContextDataProvider()).toBe(testContextDataProvider); - expect(sdk.getContextPublisher()).toBe(testContextPublisher); - - done(); - }); - - it("should pass custom agent to client", (done) => { - const customAgent = "my-custom-agent"; - const options = { - ...sdkOptions, - agent: customAgent, - }; - - const sdk = new SDK(options); - - expect(sdk).toBeInstanceOf(SDK); - expect(Client).toHaveBeenCalledTimes(1); - expect(Client).toHaveBeenCalledWith( - expect.objectContaining({ - agent: customAgent, - }) - ); - - done(); - }); - - it("should set default values for unspecified client options", (done) => { - const options = { - application: "application", - apiKey: "apikey", - endpoint: "localhost:8080", - environment: "test", - }; - - const sdk = new SDK(options); - - expect(sdk).toBeInstanceOf(SDK); - expect(sdk.getEventLogger()).toBe(SDK.defaultEventLogger); - expect(Client).toHaveBeenCalledTimes(1); - expect(Client).toHaveBeenCalledWith( - Object.assign( - {}, - { - agent: "absmartly-javascript-sdk", - }, - options - ) - ); - - done(); - }); - }); - - describe("getContextData()", () => { - it("should call client getContext and return data promise", (done) => { - const sdk = new SDK(sdkOptions); - - const promise = Promise.resolve({}); - testContextDataProvider.getContextData.mockReturnValue(promise); - - const data = sdk.getContextData(); - - expect(testContextDataProvider.getContextData).toHaveBeenCalledTimes(1); - expect(testContextDataProvider.getContextData).toHaveBeenCalledWith(sdk, undefined); - expect(data).toBe(promise); - - expect(Context).not.toHaveBeenCalled(); - - done(); - }); - }); - - describe("createContext()", () => { - it("should create Context object with promise", (done) => { - const sdk = new SDK(sdkOptions); - - const promise = Promise.resolve({}); - testContextDataProvider.getContextData.mockReturnValue(promise); - - const contextOptions = { - publishDelay: 1000, - refreshPeriod: 0, - }; - - const context = sdk.createContext(contextParams, contextOptions); - - expect(context).toBeInstanceOf(Context); - expect(testContextDataProvider.getContextData).toHaveBeenCalledTimes(1); - expect(testContextDataProvider.getContextData).toHaveBeenCalledWith(sdk, undefined); - - expect(Context).toHaveBeenCalledTimes(1); - expect(Context).toHaveBeenCalledWith(sdk, contextOptions, contextParams, promise); - - done(); - }); - - it("should pass through request options", (done) => { - const sdk = new SDK(sdkOptions); - - const promise = Promise.resolve({}); - testContextDataProvider.getContextData.mockReturnValue(promise); - - const contextOptions = { - publishDelay: 1000, - refreshPeriod: 0, - }; - - const requestOptions = { - timeout: 1234, - }; - - const context = sdk.createContext(contextParams, contextOptions, requestOptions); - - expect(context).toBeInstanceOf(Context); - expect(testContextDataProvider.getContextData).toHaveBeenCalledTimes(1); - expect(testContextDataProvider.getContextData).toHaveBeenCalledWith(sdk, requestOptions); - - expect(Context).toHaveBeenCalledTimes(1); - expect(Context).toHaveBeenCalledWith(sdk, contextOptions, contextParams, promise); - - done(); - }); - - it("should coerce unit uid to string", (done) => { - const sdk = new SDK(sdkOptions); - - const promise = Promise.resolve({}); - testContextDataProvider.getContextData.mockReturnValue(promise); - - const contextOptions = { - publishDelay: 1000, - refreshPeriod: 0, - }; - - const params = { - units: { - session_id: "ab", - user_id: 125, - float_id: 125.75, - }, - }; - - const context = sdk.createContext(params, contextOptions); - - expect(context).toBeInstanceOf(Context); - expect(testContextDataProvider.getContextData).toHaveBeenCalledTimes(1); - expect(testContextDataProvider.getContextData).toHaveBeenCalledWith(sdk, undefined); - - expect(Context).toHaveBeenCalledTimes(1); - expect(Context).toHaveBeenCalledWith(sdk, contextOptions, params, promise); - - done(); - }); - - it("should throw on unsupported unit uid type", (done) => { - const sdk = new SDK(sdkOptions); - - const promise = Promise.resolve({}); - testContextDataProvider.getContextData.mockReturnValue(promise); - - const contextOptions = { - publishDelay: 1000, - refreshPeriod: 0, - eventLogger: testEventLogger, - }; - - const params = { - units: { - session_id: true, - }, - }; - - expect(() => sdk.createContext(params, contextOptions)).toThrow( - new Error( - "Unit 'session_id' UID is of unsupported type 'boolean'. UID must be one of ['string', 'number']" - ) - ); - expect(testContextDataProvider.getContextData).not.toHaveBeenCalled(); - expect(Context).not.toHaveBeenCalled(); - - done(); - }); - - it("should throw on empty unit uid", (done) => { - const sdk = new SDK(sdkOptions); - - const promise = Promise.resolve({}); - testContextDataProvider.getContextData.mockReturnValue(promise); - - const contextOptions = { - publishDelay: 1000, - refreshPeriod: 0, - eventLogger: testEventLogger, - }; - - const params = { - units: { - session_id: "", - }, - }; - - expect(() => sdk.createContext(params, contextOptions)).toThrow( - new Error("Unit 'session_id' UID length must be >= 1") - ); - expect(testContextDataProvider.getContextData).not.toHaveBeenCalled(); - expect(Context).not.toHaveBeenCalled(); - - done(); - }); - - it("should initialize context with default options for nodejs", (done) => { - const sdk = new SDK(sdkOptions); - - const promise = Promise.resolve({}); - testContextDataProvider.getContextData.mockReturnValue(promise); - - const context = sdk.createContext(contextParams); - - const defaultOptions = { - publishDelay: -1, - refreshPeriod: 0, - }; - - expect(context).toBeInstanceOf(Context); - expect(testContextDataProvider.getContextData).toHaveBeenCalledTimes(1); - expect(testContextDataProvider.getContextData).toHaveBeenCalledWith(sdk, undefined); - - expect(context).toBeInstanceOf(Context); - expect(Context).toHaveBeenCalledTimes(1); - expect(Context).toHaveBeenCalledWith(sdk, defaultOptions, contextParams, promise); - - done(); - }); - - it("should initialize context with default options for browser", (done) => { - const sdk = new SDK(sdkOptions); - - const promise = Promise.resolve({}); - testContextDataProvider.getContextData.mockReturnValue(promise); - - // fake browser environment - const previousWindow = global.window; - global.window = { document: {} }; - - const context = sdk.createContext(contextParams); - - // restore environment - global.window = previousWindow; - - const defaultOptions = { - publishDelay: 100, - refreshPeriod: 0, - }; - - expect(context).toBeInstanceOf(Context); - expect(testContextDataProvider.getContextData).toHaveBeenCalledTimes(1); - expect(testContextDataProvider.getContextData).toHaveBeenCalledWith(sdk, undefined); - - expect(context).toBeInstanceOf(Context); - expect(Context).toHaveBeenCalledTimes(1); - expect(Context).toHaveBeenCalledWith(sdk, defaultOptions, contextParams, promise); - - done(); - }); - }); - - describe("createContextWith()", () => { - it("should not call client getContext", (done) => { - const data = { - guid: "test", - }; - - const contextOptions = { - publishDelay: 1000, - refreshPeriod: 0, - eventLogger: testEventLogger, - }; - - const sdk = new SDK(sdkOptions); - const context = sdk.createContextWith(contextParams, data, contextOptions); - - expect(context).toBeInstanceOf(Context); - expect(testContextDataProvider.getContextData).not.toHaveBeenCalled(); - - expect(Context).toHaveBeenCalledTimes(1); - expect(Context).toHaveBeenCalledWith(sdk, contextOptions, contextParams, data); - - done(); - }); - - it("should throw on unsupported unit uid type", (done) => { - const sdk = new SDK(sdkOptions); - - const contextOptions = { - publishDelay: 1000, - refreshPeriod: 0, - eventLogger: testEventLogger, - }; - - const params = { - units: { - session_id: true, - }, - }; - - expect(() => sdk.createContextWith(params, contextOptions, {})).toThrow( - new Error( - "Unit 'session_id' UID is of unsupported type 'boolean'. UID must be one of ['string', 'number']" - ) - ); - expect(testContextDataProvider.getContextData).not.toHaveBeenCalled(); - expect(Context).not.toHaveBeenCalled(); - - done(); - }); - - it("should initialize context with default options", (done) => { - const data = { - guid: "test", - }; - - const defaultOptions = { - publishDelay: -1, - refreshPeriod: 0, - }; - - const sdk = new SDK(sdkOptions); - const context = sdk.createContextWith(contextParams, data); - - expect(context).toBeInstanceOf(Context); - expect(Context).toHaveBeenCalledTimes(1); - expect(Context).toHaveBeenCalledWith(sdk, defaultOptions, contextParams, data); - - done(); - }); - }); - - describe("setEventLogger()", () => { - it("should override the current logger", (done) => { - const sdk = new SDK(sdkOptions); - expect(sdk.getEventLogger()).toBe(testEventLogger); - - const newLogger = jest.fn(); - sdk.setEventLogger(newLogger); - expect(sdk.getEventLogger()).toBe(newLogger); - - done(); - }); - }); - - describe("setContextDataProvider()", () => { - it("should override the current provider", (done) => { - const sdk = new SDK(sdkOptions); - expect(sdk.getContextDataProvider()).toBe(testContextDataProvider); - - const newProvider = new ContextDataProvider(); - sdk.setContextDataProvider(newProvider); - expect(sdk.getContextDataProvider()).toBe(newProvider); - - done(); - }); - }); - - describe("setContextPublisher()", () => { - it("should override the current publisher", (done) => { - const sdk = new SDK(sdkOptions); - expect(sdk.getContextPublisher()).toBe(testContextPublisher); - - const newPublisher = new ContextPublisher(); - sdk.setContextPublisher(newPublisher); - expect(sdk.getContextPublisher()).toBe(newPublisher); - - done(); - }); - }); - - describe("defaultLogger", () => { - it("should log only errors to console", (done) => { - const errorSpy = jest.spyOn(console, "error").mockImplementation(() => {}); - jest.spyOn(console, "warn").mockImplementation(() => {}); - jest.spyOn(console, "info").mockImplementation(() => {}); - jest.spyOn(console, "log").mockImplementation(() => {}); - - for (const eventName of ["error", "ready", "refresh", "publish", "goal", "exposure"]) { - if (eventName === "error") { - SDK.defaultEventLogger("context", eventName, "error text"); - expect(console.error).toHaveBeenCalledTimes(1); - expect(console.error).toHaveBeenCalledWith("error text"); - - errorSpy.mockClear(); - } else { - SDK.defaultEventLogger("context", eventName, {}); - expect(console.error).not.toHaveBeenCalled(); - } - expect(console.warn).not.toHaveBeenCalled(); - expect(console.info).not.toHaveBeenCalled(); - expect(console.log).not.toHaveBeenCalled(); - } - - done(); - }); - }); -}); diff --git a/src/__tests__/utils.test.js b/src/__tests__/utils.test.js deleted file mode 100644 index 39cf8d4..0000000 --- a/src/__tests__/utils.test.js +++ /dev/null @@ -1,343 +0,0 @@ -import { - arrayEqualsShallow, - base64UrlNoPadding, - chooseVariant, - hashUnit, - isEqualsDeep, - isNumeric, - isObject, - isPromise, - stringToUint8Array, -} from "../utils"; - -class SomeClass {} - -describe("isObject()", () => { - it("should return true with objects", (done) => { - expect(isObject({})).toBe(true); - expect(isObject(new Object())).toBe(true); - - done(); - }); - - it("should return false with common non-object", (done) => { - expect(isObject(null)).toBe(false); - expect(isObject([])).toBe(false); - expect(isObject(1)).toBe(false); - expect(isObject(true)).toBe(false); - expect(isObject(false)).toBe(false); - expect(isObject("str")).toBe(false); - expect(isObject(new Uint8Array(1))).toBe(false); - expect(isObject(new Map())).toBe(false); - expect(isObject(new SomeClass())).toBe(false); - - done(); - }); -}); - -describe("isNumeric()", () => { - it("should return true with numbers", (done) => { - expect(isNumeric(1)).toBe(true); - expect(isNumeric(1.5)).toBe(true); - - done(); - }); - - it("should return false with non-numbers", (done) => { - expect(isNumeric(null)).toBe(false); - expect(isNumeric("1")).toBe(false); - expect(isNumeric(true)).toBe(false); - expect(isNumeric(false)).toBe(false); - expect(isNumeric([])).toBe(false); - expect(isNumeric({})).toBe(false); - expect(isNumeric(new Object())).toBe(false); - expect(isNumeric(new Map())).toBe(false); - - done(); - }); -}); - -describe("isPromise()", () => { - it("should return true with a thenable object", (done) => { - expect( - isPromise({ - then() {}, - }) - ).toBe(true); - - done(); - }); - - it("should return false with non objects or objects without then function", (done) => { - expect(isPromise(null)).toBe(false); - expect(isPromise([])).toBe(false); - expect(isPromise({})).toBe(false); - expect(isPromise(1)).toBe(false); - expect(isPromise(true)).toBe(false); - expect(isPromise(false)).toBe(false); - expect(isPromise("str")).toBe(false); - expect(isPromise(new Uint8Array(1))).toBe(false); - - done(); - }); -}); - -describe("isEqualsDeep()", () => { - it("should return true with two NaNs", (done) => { - expect(isEqualsDeep(Number.NaN, Number.NaN)).toBe(true); - - done(); - }); - - it("should return true iff basic primitives are equal", (done) => { - expect(isEqualsDeep(0, 0)).toBe(true); - expect(isEqualsDeep(1, 1)).toBe(true); - expect(isEqualsDeep(1.5, 1.5)).toBe(true); - expect(isEqualsDeep("", "")).toBe(true); - expect(isEqualsDeep("abc", "abc")).toBe(true); - expect(isEqualsDeep(true, true)).toBe(true); - expect(isEqualsDeep(false, false)).toBe(true); - - expect(isEqualsDeep(0, 1)).toBe(false); - expect(isEqualsDeep(1, 1.5)).toBe(false); - expect(isEqualsDeep(1.5, "")).toBe(false); - expect(isEqualsDeep("", "abc")).toBe(false); - expect(isEqualsDeep("abc", true)).toBe(false); - expect(isEqualsDeep(true, false)).toBe(false); - expect(isEqualsDeep(false, 0)).toBe(false); - - done(); - }); - - it("should return true iff arrays are equal", (done) => { - expect(isEqualsDeep([1, 2, 3], [1, 2, 3])).toBe(true); - expect(isEqualsDeep([1, 2, 3], [3, 2, 1])).toBe(false); - - expect(isEqualsDeep([], [1])).toBe(false); - expect(isEqualsDeep([1], [])).toBe(false); - - expect(isEqualsDeep([1, 2, [3, 4, [5, 6]]], [1, 2, [3, 4, [5, 6]]])).toBe(true); - expect(isEqualsDeep([1, 2, [3, 4, [5, 6]]], [1, 2, [3, 4, [5, 9]]])).toBe(false); - - expect(isEqualsDeep(["a", "b", ["c", "d", ["e", "f"]]], ["a", "b", ["c", "d", ["e", "f"]]])).toBe(true); - expect(isEqualsDeep(["a", "b", ["c", "d", ["e", "f"]]], ["a", "b", ["c", "d", ["e", "x"]]])).toBe(false); - - done(); - }); - - it("should return true iff arrays with circular references are equal", (done) => { - const a = [1, 2, [3, 4, [5, 6]]]; - a[1] = a; - - const b = [1, 2, [3, 4, [5, 6]]]; - b[1] = b; - - expect(isEqualsDeep(a, b)).toBe(true); - - a[2][2][0] = a; - b[2][2][0] = b; - - expect(isEqualsDeep(a, b)).toBe(true); - - b[2][2][1] = 9; - - expect(isEqualsDeep(a, b)).toBe(false); - - done(); - }); - - it("should return true iff objects are equal", (done) => { - expect(isEqualsDeep({ a: 1, b: 2, c: 3 }, { a: 1, b: 2, c: 3 })).toBe(true); - expect(isEqualsDeep({ a: 1, b: 2, c: 3 }, { c: 3, b: 2, a: 1 })).toBe(true); - expect(isEqualsDeep({ a: 1, b: 2, c: 3 }, { a: 3, b: 2, c: 1 })).toBe(false); - - expect(isEqualsDeep({}, { a: 1 })).toBe(false); - expect(isEqualsDeep({ a: 1 }, {})).toBe(false); - - expect( - isEqualsDeep( - { a: 1, b: 2, c: { d: 3, e: 4, f: { g: 5, h: 6 } } }, - { a: 1, b: 2, c: { d: 3, e: 4, f: { g: 5, h: 6 } } } - ) - ).toBe(true); - expect( - isEqualsDeep( - { a: 1, b: 2, c: { d: 3, e: 4, f: { g: 5, h: 6 } } }, - { a: 1, b: 2, c: { d: 3, e: 4, f: { g: 5, h: 9 } } } - ) - ).toBe(false); - - expect( - isEqualsDeep( - { a: "a", b: "b", c: { d: "c", e: "d", f: { g: "e", h: "f" } } }, - { a: "a", b: "b", c: { d: "c", e: "d", f: { g: "e", h: "f" } } } - ) - ).toBe(true); - expect( - isEqualsDeep( - { a: "a", b: "b", c: { d: "c", e: "d", f: { g: "e", h: "f" } } }, - { a: "a", b: "b", c: { d: "c", e: "d", f: { g: "e", h: "x" } } } - ) - ).toBe(false); - - done(); - }); - - it("should return true iff arrays with circular references are equal", (done) => { - const a = { a: 1, b: 2, c: { d: 3, e: 4, f: { g: 5, h: 6 } } }; - a.b = a; - - const b = { a: 1, b: 2, c: { d: 3, e: 4, f: { g: 5, h: 6 } } }; - b.b = b; - - expect(isEqualsDeep(a, b)).toBe(true); - - a.c.f.g = a; - b.c.f.g = b; - - expect(isEqualsDeep(a, b)).toBe(true); - - b.c.f.h = 9; - - expect(isEqualsDeep(a, b)).toBe(false); - - done(); - }); -}); - -describe("arrayEqualsShallow()", () => { - it("should return true only with equal arrays", (done) => { - expect(arrayEqualsShallow([], [])).toEqual(true); - expect(arrayEqualsShallow([1], [1])).toEqual(true); - expect(arrayEqualsShallow([1, 2, 3], [1, 2, 3])).toEqual(true); - expect(arrayEqualsShallow([0.5, 1.0, 1.5], [0.5, 1.0, 1.5])).toEqual(true); - - expect(arrayEqualsShallow([1], ["1"])).toEqual(false); - expect(arrayEqualsShallow([1, 2, 3], [1, 2, "3"])).toEqual(false); - expect(arrayEqualsShallow([1], [])).toEqual(false); - expect(arrayEqualsShallow([], [1])).toEqual(false); - expect(arrayEqualsShallow([1, 2, 3], [1, 2])).toEqual(false); - expect(arrayEqualsShallow([1, 2], [1, 2, 3])).toEqual(false); - - done(); - }); -}); - -describe("hashUnit()", () => { - it("should return matching hashes", (done) => { - expect(hashUnit("4a42766ca6313d26f49985e799ff4f3790fb86efa0fce46edb3ea8fbf1ea3408")).toBe( - "H2jvj6o9YcAgNdhKqEbtWw" - ); - expect(hashUnit("bleh@absmarty.com")).toBe("DRgslOje35bZMmpaohQjkA"); - expect(hashUnit("açb↓c")).toBe("LxcqH5VC15rXfWfA_smreg"); - expect(hashUnit("testy")).toBe("K5I_V6RgP8c6sYKz-TVn8g"); - expect(hashUnit(123456778999)).toBe("K4uy4bTeCy34W97lmceVRg"); - - done(); - }); -}); - -describe("chooseVariant()", () => { - it("should return correct variant", (done) => { - expect(chooseVariant([0.0, 1.0], 0.0)).toBe(1); - expect(chooseVariant([0.0, 1.0], 0.5)).toBe(1); - expect(chooseVariant([0.0, 1.0], 1.0)).toBe(1); - - expect(chooseVariant([1.0, 0.0], 0.0)).toBe(0); - expect(chooseVariant([1.0, 0.0], 0.5)).toBe(0); - expect(chooseVariant([1.0, 0.0], 1.0)).toBe(1); - - expect(chooseVariant([0.5, 0.5], 0.0)).toBe(0); - expect(chooseVariant([0.5, 0.5], 0.25)).toBe(0); - expect(chooseVariant([0.5, 0.5], 0.49999999)).toBe(0); - expect(chooseVariant([0.5, 0.5], 0.5)).toBe(1); - expect(chooseVariant([0.5, 0.5], 0.50000001)).toBe(1); - expect(chooseVariant([0.5, 0.5], 0.75)).toBe(1); - expect(chooseVariant([0.5, 0.5], 1.0)).toBe(1); - - expect(chooseVariant([0.333, 0.333, 0.334], 0.0)).toBe(0); - expect(chooseVariant([0.333, 0.333, 0.334], 0.25)).toBe(0); - expect(chooseVariant([0.333, 0.333, 0.334], 0.33299999)).toBe(0); - expect(chooseVariant([0.333, 0.333, 0.334], 0.333)).toBe(1); - expect(chooseVariant([0.333, 0.333, 0.334], 0.33300001)).toBe(1); - expect(chooseVariant([0.333, 0.333, 0.334], 0.5)).toBe(1); - expect(chooseVariant([0.333, 0.333, 0.334], 0.66599999)).toBe(1); - expect(chooseVariant([0.333, 0.333, 0.334], 0.666)).toBe(2); - expect(chooseVariant([0.333, 0.333, 0.334], 0.66600001)).toBe(2); - expect(chooseVariant([0.333, 0.333, 0.334], 0.75)).toBe(2); - expect(chooseVariant([0.333, 0.333, 0.334], 1.0)).toBe(2); - - done(); - }); -}); - -describe("stringToUint8Array()", () => { - it("should encode special characters to utf8", (done) => { - const testCases = [ - ["", Uint8Array.from([])], - [" ", Uint8Array.from([32])], - ["a", Uint8Array.from([97])], - ["ab", Uint8Array.from([97, 98])], - ["abc", Uint8Array.from([97, 98, 99])], - ["abcd", Uint8Array.from([97, 98, 99, 100])], - ["ç", Uint8Array.from([195, 167])], - ["aç", Uint8Array.from([97, 195, 167])], - ["çb", Uint8Array.from([195, 167, 98])], - ["açb", Uint8Array.from([97, 195, 167, 98])], - ["↓", Uint8Array.from([226, 134, 147])], - ["a↓", Uint8Array.from([97, 226, 134, 147])], - ["↓b", Uint8Array.from([226, 134, 147, 98])], - ["a↓b", Uint8Array.from([97, 226, 134, 147, 98])], - ["aç↓", Uint8Array.from([97, 195, 167, 226, 134, 147])], - ["aç↓b", Uint8Array.from([97, 195, 167, 226, 134, 147, 98])], - ["açb↓c", Uint8Array.from([97, 195, 167, 98, 226, 134, 147, 99])], - ]; - - for (const testCase of testCases) { - const array = stringToUint8Array(testCase[0]); - const expected = testCase[1]; - expect(array.length).toBe(expected.length); - for (let i = 0; i < array.length; ++i) { - expect(array[i]).toBe(expected[i]); - } - } - done(); - }); -}); - -describe("base64UrlNoPadding()", () => { - it("should match known encodings", (done) => { - const testCases = [ - ["", ""], - [" ", "IA"], - ["t", "dA"], - ["te", "dGU"], - ["tes", "dGVz"], - ["test", "dGVzdA"], - ["testy", "dGVzdHk"], - ["testy1", "dGVzdHkx"], - ["testy12", "dGVzdHkxMg"], - ["testy123", "dGVzdHkxMjM"], - ["special characters açb↓c", "c3BlY2lhbCBjaGFyYWN0ZXJzIGHDp2LihpNj"], - [ - "The quick brown fox jumps over the lazy dog", - "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw", - ], - [ - "The quick brown fox jumps over the lazy dog and eats a pie", - "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZyBhbmQgZWF0cyBhIHBpZQ", - ], - [ - "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdCwgc2VkIGRvIGVpdXNtb2QgdGVtcG9yIGluY2lkaWR1bnQgdXQgbGFib3JlIGV0IGRvbG9yZSBtYWduYSBhbGlxdWEuIFV0IGVuaW0gYWQgbWluaW0gdmVuaWFtLCBxdWlzIG5vc3RydWQgZXhlcmNpdGF0aW9uIHVsbGFtY28gbGFib3JpcyBuaXNpIHV0IGFsaXF1aXAgZXggZWEgY29tbW9kbyBjb25zZXF1YXQuIER1aXMgYXV0ZSBpcnVyZSBkb2xvciBpbiByZXByZWhlbmRlcml0IGluIHZvbHVwdGF0ZSB2ZWxpdCBlc3NlIGNpbGx1bSBkb2xvcmUgZXUgZnVnaWF0IG51bGxhIHBhcmlhdHVyLiBFeGNlcHRldXIgc2ludCBvY2NhZWNhdCBjdXBpZGF0YXQgbm9uIHByb2lkZW50LCBzdW50IGluIGN1bHBhIHF1aSBvZmZpY2lhIGRlc2VydW50IG1vbGxpdCBhbmltIGlkIGVzdCBsYWJvcnVtLg", - ], - ]; - - testCases.forEach((testCase) => { - const bytes = stringToUint8Array(testCase[0]); - expect(base64UrlNoPadding(bytes)).toEqual(testCase[1]); - }); - - done(); - }); -}); diff --git a/src/abort-controller-shim.ts b/src/abort-controller-shim.ts deleted file mode 100644 index 1456e47..0000000 --- a/src/abort-controller-shim.ts +++ /dev/null @@ -1,90 +0,0 @@ -export type AbortControllerEvents = { - [key: string]: Array<() => unknown>; -}; - -// eslint-disable-next-line no-shadow -export class AbortSignal { - aborted = false; - private readonly _events: AbortControllerEvents; - - constructor() { - this._events = {}; - } - - addEventListener(type: string, listener: () => void) { - let listeners = this._events[type]; - if (!listeners) { - listeners = []; - this._events[type] = listeners; - } - listeners.push(listener); - } - - removeEventListener(type: string, listener: () => void) { - const listeners = this._events[type]; - if (listeners) { - const index = listeners.findIndex((x) => x === listener); - if (index !== -1) { - listeners.splice(index, 1); - if (listeners.length === 0) { - delete this._events[type]; - } - } - } - } - - dispatchEvent(evt: { type: string }) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - this[`on${evt.type}`] && this[`on${evt.type}`](evt); - const listeners = this._events[evt.type]; - if (listeners) { - for (const listener of listeners) { - listener.call(null, evt); - } - } - } - - toString() { - return "[object AbortSignal]"; - } -} - -// eslint-disable-next-line no-shadow -export class AbortController { - signal = new AbortSignal(); - - abort() { - let evt: Event | { type: string; bubbles: boolean; cancelable: boolean }; - try { - evt = new Event("abort"); - } catch (e) { - evt = { - type: "abort", - bubbles: false, - cancelable: false, - }; - } - - this.signal.aborted = true; - this.signal.dispatchEvent(evt); - } - - toString() { - return "[object AbortController]"; - } -} - -if (typeof Symbol !== "undefined" && Symbol.toStringTag !== undefined) { - Object.defineProperty(AbortSignal.prototype, Symbol.toStringTag, { - configurable: true, - value: "AbortSignal", - }); - - Object.defineProperty(AbortController.prototype, Symbol.toStringTag, { - configurable: true, - value: "AbortController", - }); -} - -export default AbortController; diff --git a/src/abort.ts b/src/abort.ts deleted file mode 100644 index 563ff75..0000000 --- a/src/abort.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { isLongLivedApp, isWorker } from "./utils"; -import AbortControllerShim from "./abort-controller-shim"; - -// eslint-disable-next-line no-shadow -export const AbortController = - isLongLivedApp() && window.AbortController - ? window.AbortController - : isWorker() && self.AbortController - ? self.AbortController - : global && global.AbortController - ? global.AbortController - : AbortControllerShim; diff --git a/src/browser.ts b/src/browser.ts deleted file mode 100644 index c3399d1..0000000 --- a/src/browser.ts +++ /dev/null @@ -1,9 +0,0 @@ -import Context from "./context"; -import SDK from "./sdk"; -import { mergeConfig } from "./config"; -import { ContextDataProvider } from "./provider"; -import { ContextPublisher } from "./publisher"; -// eslint-disable-next-line no-shadow -import { AbortController } from "./abort"; - -export default { mergeConfig, AbortController, Context, ContextDataProvider, ContextPublisher, SDK }; diff --git a/src/fetch-shim.ts b/src/fetch-shim.ts deleted file mode 100644 index b391e74..0000000 --- a/src/fetch-shim.ts +++ /dev/null @@ -1,81 +0,0 @@ -// inspired by https://github.com/bradlc/unfetch-abortable -import { AbortError } from "./errors"; - -export type FetchOptions = { - signal?: AbortSignal; - method?: "get" | "post" | "put" | "patch" | "delete" | "head" | "options"; - credentials?: "include"; - headers?: Record; - body?: XMLHttpRequestBodyInit; -}; -// eslint-disable-next-line no-shadow -export function fetch(url: string, options: FetchOptions) { - options = options || {}; - return new Promise((resolve, reject) => { - const request = new XMLHttpRequest(); - const keys: string[] = []; - const all: [string, unknown][] = []; - const headers: Record = {}; - - const abort = () => { - request.abort(); - }; - - const cleanup = options.signal ? () => options.signal?.removeEventListener("abort", abort) : () => {}; - - const response = () => ({ - ok: ((request.status / 100) | 0) === 2, - statusText: request.statusText, - status: request.status, - url: request.responseURL, - text: () => Promise.resolve(request.responseText), - json: () => Promise.resolve(JSON.parse(request.responseText)), - blob: () => Promise.resolve(new Blob([request.response])), - clone: response, - headers: { - keys: () => keys, - entries: () => all, - get: (n: string) => headers[n.toLowerCase()], - has: (n: string) => n.toLowerCase() in headers, - }, - }); - - request.open(options.method || "get", url, true); - - request.onload = () => { - request.getAllResponseHeaders().replace(/^(.*?):[^\S\n]*([\s\S]*?)$/gm, (m, key, value) => { - keys.push((key = key.toLowerCase())); - all.push([key, value]); - headers[key] = headers[key] ? `${headers[key]},${value}` : value; - return m; - }); - - cleanup(); - resolve(response()); - }; - - if (options.signal) { - options.signal.addEventListener("abort", abort); - } - - request.onerror = (error) => { - cleanup(); - reject(error); - }; - - request.onabort = () => { - cleanup(); - reject(new AbortError("The user aborted a request.")); - }; - - request.withCredentials = options.credentials === "include"; - - for (const i in options.headers) { - request.setRequestHeader(i, options.headers[i]); - } - - request.send(options.body || null); - }); -} - -export default fetch; diff --git a/src/fetch.ts b/src/fetch.ts deleted file mode 100644 index 088f22e..0000000 --- a/src/fetch.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { isLongLivedApp, isWorker } from "./utils"; -import fetchShim from "./fetch-shim"; - -const exported = isLongLivedApp() - ? window.fetch - ? window.fetch.bind(window) - : fetchShim - : isWorker() - ? self.fetch - ? self.fetch.bind(self) - : fetchShim - : global - ? global.fetch - ? global.fetch.bind(global) - : function (url: string, opts: Record) { - return new Promise((resolve, reject) => { - import("node-fetch") - .then((fetchNode) => { - fetchNode.default(url.replace(/^\/\//g, "https://"), opts).then(resolve).catch(reject); - }) - .catch(reject); - }); - } - : undefined; - -export default exported; diff --git a/src/jsonexpr/operators/and.ts b/src/jsonexpr/operators/and.ts deleted file mode 100644 index e3d7177..0000000 --- a/src/jsonexpr/operators/and.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Evaluator } from "../evaluator"; - -export class AndCombinator { - evaluate(evaluator: Evaluator, args: unknown[]): boolean | null { - if (Array.isArray(args)) { - for (const expr of args) { - if (!evaluator.booleanConvert(evaluator.evaluate(expr))) { - return false; - } - } - return true; - } - return null; - } -} diff --git a/src/jsonexpr/operators/binary.ts b/src/jsonexpr/operators/binary.ts deleted file mode 100644 index 99ec1aa..0000000 --- a/src/jsonexpr/operators/binary.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Evaluator } from "../evaluator"; - -export abstract class BinaryOperator { - abstract binary(evaluator: Evaluator, lhs: unknown, rhs: unknown): boolean | null; - - evaluate(evaluator: Evaluator, args: unknown[]) { - if (Array.isArray(args)) { - const lhs = args.length > 0 ? evaluator.evaluate(args[0]) : null; - if (lhs !== null) { - const rhs = args.length > 1 ? evaluator.evaluate(args[1]) : null; - if (rhs !== null) { - return this.binary(evaluator, lhs, rhs); - } - } - } - return null; - } -} diff --git a/src/jsonexpr/operators/eq.ts b/src/jsonexpr/operators/eq.ts deleted file mode 100644 index bc71f8c..0000000 --- a/src/jsonexpr/operators/eq.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Evaluator } from "../evaluator"; -import { BinaryOperator } from "./binary"; - -export class EqualsOperator extends BinaryOperator { - binary(evaluator: Evaluator, lhs: unknown, rhs: unknown) { - const result = evaluator.compare(lhs, rhs); - return result !== null ? result === 0 : null; - } -} diff --git a/src/jsonexpr/operators/gt.ts b/src/jsonexpr/operators/gt.ts deleted file mode 100644 index 60735a4..0000000 --- a/src/jsonexpr/operators/gt.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Evaluator } from "../evaluator"; -import { BinaryOperator } from "./binary"; - -export class GreaterThanOperator extends BinaryOperator { - binary(evaluator: Evaluator, lhs: unknown, rhs: unknown) { - const result = evaluator.compare(lhs, rhs); - return result !== null ? result > 0 : null; - } -} diff --git a/src/jsonexpr/operators/gte.ts b/src/jsonexpr/operators/gte.ts deleted file mode 100644 index c5a220f..0000000 --- a/src/jsonexpr/operators/gte.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Evaluator } from "../evaluator"; -import { BinaryOperator } from "./binary"; - -export class GreaterThanOrEqualOperator extends BinaryOperator { - binary(evaluator: Evaluator, lhs: unknown, rhs: unknown) { - const result = evaluator.compare(lhs, rhs); - return result !== null ? result >= 0 : null; - } -} diff --git a/src/jsonexpr/operators/in.ts b/src/jsonexpr/operators/in.ts deleted file mode 100644 index fa0cf28..0000000 --- a/src/jsonexpr/operators/in.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { BinaryOperator } from "./binary"; -import { isObject } from "../../utils"; -import { Evaluator } from "../evaluator"; - -export class InOperator extends BinaryOperator { - binary(evaluator: Evaluator, haystack: unknown, needle: string | number | boolean | null) { - if (Array.isArray(haystack)) { - for (const item of haystack) { - if (evaluator.compare(item, needle) === 0) { - return true; - } - } - return false; - } else if (typeof haystack === "string") { - const needleString = evaluator.stringConvert(needle); - return needleString !== null && haystack.includes(needleString); - } else if (isObject(haystack)) { - const needleString = evaluator.stringConvert(needle); - return needleString != null && Object.prototype.hasOwnProperty.call(haystack, needleString); - } - return null; - } -} diff --git a/src/jsonexpr/operators/lt.ts b/src/jsonexpr/operators/lt.ts deleted file mode 100644 index 77e3e2d..0000000 --- a/src/jsonexpr/operators/lt.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Evaluator } from "../evaluator"; -import { BinaryOperator } from "./binary"; - -export class LessThanOperator extends BinaryOperator { - binary(evaluator: Evaluator, lhs: unknown, rhs: unknown) { - const result = evaluator.compare(lhs, rhs); - return result !== null ? result < 0 : null; - } -} diff --git a/src/jsonexpr/operators/lte.ts b/src/jsonexpr/operators/lte.ts deleted file mode 100644 index b2f6da3..0000000 --- a/src/jsonexpr/operators/lte.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Evaluator } from "../evaluator"; -import { BinaryOperator } from "./binary"; - -export class LessThanOrEqualOperator extends BinaryOperator { - binary(evaluator: Evaluator, lhs: unknown, rhs: unknown) { - const result = evaluator.compare(lhs, rhs); - return result !== null ? result <= 0 : null; - } -} diff --git a/src/jsonexpr/operators/match.ts b/src/jsonexpr/operators/match.ts deleted file mode 100644 index a18be52..0000000 --- a/src/jsonexpr/operators/match.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Evaluator } from "../evaluator"; -import { BinaryOperator } from "./binary"; - -export class MatchOperator extends BinaryOperator { - binary(evaluator: Evaluator, text: string | null, pattern: string | null) { - text = evaluator.stringConvert(text); - if (text !== null) { - pattern = evaluator.stringConvert(pattern); - if (pattern !== null) { - try { - const compiled = new RegExp(pattern); - return compiled.test(text); - } catch (ignored) { - return null; - } - } - } - return null; - } -} diff --git a/src/jsonexpr/operators/not.ts b/src/jsonexpr/operators/not.ts deleted file mode 100644 index 395120a..0000000 --- a/src/jsonexpr/operators/not.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Evaluator } from "../evaluator"; -import { UnaryOperator } from "./unary"; - -export class NotOperator extends UnaryOperator { - unary(evaluator: Evaluator, arg: string | number | boolean) { - return !evaluator.booleanConvert(arg); - } -} diff --git a/src/jsonexpr/operators/null.ts b/src/jsonexpr/operators/null.ts deleted file mode 100644 index fbbc442..0000000 --- a/src/jsonexpr/operators/null.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Evaluator } from "../evaluator"; -import { UnaryOperator } from "./unary"; - -export class NullOperator extends UnaryOperator { - unary(_: Evaluator, value: unknown) { - return value === null; - } -} diff --git a/src/jsonexpr/operators/or.ts b/src/jsonexpr/operators/or.ts deleted file mode 100644 index 62f6b2d..0000000 --- a/src/jsonexpr/operators/or.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Evaluator } from "../evaluator"; - -export class OrCombinator { - evaluate(evaluator: Evaluator, args: unknown) { - if (Array.isArray(args)) { - for (const expr of args) { - if (evaluator.booleanConvert(evaluator.evaluate(expr))) { - return true; - } - } - return args.length === 0; - } - return null; - } -} diff --git a/src/jsonexpr/operators/semver_eq.ts b/src/jsonexpr/operators/semver_eq.ts deleted file mode 100644 index d35dfc2..0000000 --- a/src/jsonexpr/operators/semver_eq.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Evaluator } from "../evaluator"; -import { BinaryOperator } from "./binary"; - -export class SemverEqualsOperator extends BinaryOperator { - binary(evaluator: Evaluator, lhs: unknown, rhs: unknown) { - const result = evaluator.versionCompare(lhs, rhs); - return result !== null ? result === 0 : null; - } -} diff --git a/src/jsonexpr/operators/semver_gt.ts b/src/jsonexpr/operators/semver_gt.ts deleted file mode 100644 index 1b41166..0000000 --- a/src/jsonexpr/operators/semver_gt.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Evaluator } from "../evaluator"; -import { BinaryOperator } from "./binary"; - -export class SemverGreaterThanOperator extends BinaryOperator { - binary(evaluator: Evaluator, lhs: unknown, rhs: unknown) { - const result = evaluator.versionCompare(lhs, rhs); - return result !== null ? result > 0 : null; - } -} diff --git a/src/jsonexpr/operators/semver_gte.ts b/src/jsonexpr/operators/semver_gte.ts deleted file mode 100644 index 8525453..0000000 --- a/src/jsonexpr/operators/semver_gte.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Evaluator } from "../evaluator"; -import { BinaryOperator } from "./binary"; - -export class SemverGreaterThanOrEqualOperator extends BinaryOperator { - binary(evaluator: Evaluator, lhs: unknown, rhs: unknown) { - const result = evaluator.versionCompare(lhs, rhs); - return result !== null ? result >= 0 : null; - } -} diff --git a/src/jsonexpr/operators/semver_lt.ts b/src/jsonexpr/operators/semver_lt.ts deleted file mode 100644 index aa44efd..0000000 --- a/src/jsonexpr/operators/semver_lt.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Evaluator } from "../evaluator"; -import { BinaryOperator } from "./binary"; - -export class SemverLessThanOperator extends BinaryOperator { - binary(evaluator: Evaluator, lhs: unknown, rhs: unknown) { - const result = evaluator.versionCompare(lhs, rhs); - return result !== null ? result < 0 : null; - } -} diff --git a/src/jsonexpr/operators/semver_lte.ts b/src/jsonexpr/operators/semver_lte.ts deleted file mode 100644 index 8d6261f..0000000 --- a/src/jsonexpr/operators/semver_lte.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Evaluator } from "../evaluator"; -import { BinaryOperator } from "./binary"; - -export class SemverLessThanOrEqualOperator extends BinaryOperator { - binary(evaluator: Evaluator, lhs: unknown, rhs: unknown) { - const result = evaluator.versionCompare(lhs, rhs); - return result !== null ? result <= 0 : null; - } -} diff --git a/src/jsonexpr/operators/unary.ts b/src/jsonexpr/operators/unary.ts deleted file mode 100644 index ee27089..0000000 --- a/src/jsonexpr/operators/unary.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Evaluator } from "../evaluator"; - -export abstract class UnaryOperator { - abstract unary(evaluator: Evaluator, arg: unknown[] | Record | string | number | boolean): boolean; - evaluate(evaluator: Evaluator, arg: unknown[] | Record | string | number | boolean) { - arg = evaluator.evaluate(arg); - return this.unary(evaluator, arg); - } -} diff --git a/src/jsonexpr/operators/value.ts b/src/jsonexpr/operators/value.ts deleted file mode 100644 index c5081c7..0000000 --- a/src/jsonexpr/operators/value.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Evaluator } from "../evaluator"; - -export class ValueOperator { - evaluate(_: Evaluator, value: unknown) { - return value; - } -} diff --git a/src/jsonexpr/operators/var.ts b/src/jsonexpr/operators/var.ts deleted file mode 100644 index 2da792f..0000000 --- a/src/jsonexpr/operators/var.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { isObject } from "../../utils"; -import { Evaluator } from "../evaluator"; - -export class VarOperator { - evaluate(evaluator: Evaluator, path: unknown) { - if (isObject(path)) { - path = (path as { path: string }).path; - } - - return typeof path === "string" ? evaluator.extractVar(path) : null; - } -} diff --git a/src/murmur3_32.ts b/src/murmur3_32.ts deleted file mode 100644 index 5d014cd..0000000 --- a/src/murmur3_32.ts +++ /dev/null @@ -1,57 +0,0 @@ -const C1 = 0xcc9e2d51; -const C2 = 0x1b873593; -const C3 = 0xe6546b64; - -const imul32 = Math.imul; - -function fmix32(h: number) { - h ^= h >>> 16; - h = imul32(h, 0x85ebca6b); - h ^= h >>> 13; - h = imul32(h, 0xc2b2ae35); - h ^= h >>> 16; - - return h >>> 0; -} - -function rotl32(a: number, b: number) { - return (a << b) | (a >>> (32 - b)); -} - -function scramble32(block: number) { - return imul32(rotl32(imul32(block, C1), 15), C2); -} - -export function murmur3_32(key: ArrayBufferLike, hash?: number) { - hash = (hash || 0) >>> 0; - const dataView = new DataView(key); - - let i; - const n = dataView.byteLength & ~3; - for (i = 0; i < n; i += 4) { - const chunk = dataView.getUint32(i, true); - hash ^= scramble32(chunk); - hash = rotl32(hash, 13); - hash = imul32(hash, 5) + C3; - } - - let remaining = 0; - switch (dataView.byteLength & 3) { - case 3: - remaining ^= dataView.getUint8(i + 2) << 16; - // fallthrough - case 2: - remaining ^= dataView.getUint8(i + 1) << 8; - // fallthrough - case 1: - remaining ^= dataView.getUint8(i); - hash ^= scramble32(remaining); - // fallthrough - default: - break; - } - - hash ^= dataView.byteLength; - hash = fmix32(hash); - return hash >>> 0; -} diff --git a/src/rfdc.d.ts b/src/rfdc.d.ts deleted file mode 100644 index 8399146..0000000 --- a/src/rfdc.d.ts +++ /dev/null @@ -1 +0,0 @@ -declare module "rfdc/default"; From 9acb1f770c5893d5d24d0b25fa9e20b6b8f09f71 Mon Sep 17 00:00:00 2001 From: Jonas Alves Date: Mon, 30 Mar 2026 09:52:57 -0300 Subject: [PATCH 19/22] feat: add optional legacy support with injectable polyfills and ES2015 browser build --- README.md | 280 +++++++++++++++++++++-------------- package.json | 2 +- src/__tests__/client.test.ts | 25 ++++ src/client.ts | 9 +- src/config.ts | 18 ++- src/types.ts | 2 + tsup.config.ts | 41 +++-- 7 files changed, 250 insertions(+), 127 deletions(-) diff --git a/README.md b/README.md index be4a960..552b092 100644 --- a/README.md +++ b/README.md @@ -4,14 +4,58 @@ A/B Smartly - JavaScript SDK ## Compatibility -The A/B Smartly Javascript SDK is an isomorphic library for Node.js (CommonJS and ES6) and browsers (UMD). +The A/B Smartly JavaScript SDK is an isomorphic TypeScript library for Node.js (ESM and CommonJS) and browsers (IIFE). -It's supported on Node.js version 6.x and npm 3.x or later. +### Modern (default, zero dependencies) +- **Node.js 18+** - uses native `fetch` and `AbortController` +- **All modern browsers** - Chrome, Firefox, Safari, Edge -It's supported on IE 10+ and all the other major browsers. +### Legacy Node.js (14-17) +Supported via optional polyfill injection. No extra dependencies are bundled - you provide your own: -**Note**: IE 10 does not natively support Promises. -If you target IE 10, you must include a polyfill like [es6-promise](https://www.npmjs.com/package/es6-promise) or [rsvp](https://www.npmjs.com/package/rsvp). +```typescript +import fetch from "node-fetch"; +import { AbortController } from "abort-controller"; + +const sdk = new SDK({ + endpoint: "https://sandbox.absmartly.io/v1", + apiKey: process.env.ABSMARTLY_API_KEY, + environment: "production", + application: "website", + fetchImpl: fetch, + AbortControllerImpl: AbortController, +}); +``` + +### Legacy Browsers (IE11+, old Safari, old Android) +A pre-built legacy bundle transpiled to ES2015 is available at `dist/index.legacy.js`. +You must provide polyfills for missing APIs: + +| API | Polyfill | +|---|---| +| `Promise` | [es6-promise](https://www.npmjs.com/package/es6-promise) | +| `fetch` | [whatwg-fetch](https://www.npmjs.com/package/whatwg-fetch) | +| `AbortController` | [abortcontroller-polyfill](https://www.npmjs.com/package/abortcontroller-polyfill) | + +```html + + + + + + + +``` + +### Build Outputs + +| File | Target | Use case | +|---|---|---| +| `dist/index.js` | ES2022 ESM | Modern bundlers (Vite, webpack, Rollup) | +| `dist/index.cjs` | ES2022 CJS | Node.js `require()` | +| `dist/index.global.js` | ES2022 IIFE | Modern browsers via ` + +``` -Simply add the following code to your `head` section to include the latest published version. +#### Directly in the browser (legacy / IE11+) ```html - + + + ``` ## Getting Started @@ -43,10 +96,9 @@ Please follow the [installation](#installation) instructions before trying the f #### Initialization This example assumes an Api Key, an Application, and an Environment have been created in the A/B Smartly web console. -```javascript -// somewhere in your application initialization code -const sdk = new absmartly.SDK({ - endpoint: 'https://sandbox.absmartly.io/v1', +```typescript +const sdk = new SDK({ + endpoint: "https://sandbox.absmartly.io/v1", apiKey: process.env.ABSMARTLY_API_KEY, environment: process.env.NODE_ENV, application: process.env.APPLICATION_NAME, @@ -54,49 +106,56 @@ const sdk = new absmartly.SDK({ ``` The `application` option can also be an object with `name` and `version` to track which version of your application is generating events. The version can be a number or a semver string: -```javascript -const sdk = new absmartly.SDK({ - endpoint: 'https://sandbox.absmartly.io/v1', +```typescript +const sdk = new SDK({ + endpoint: "https://sandbox.absmartly.io/v1", apiKey: process.env.ABSMARTLY_API_KEY, environment: process.env.NODE_ENV, - application: { name: 'website', version: '1.2.3' }, + application: { name: "website", version: "1.2.3" }, }); ``` -#### Creating a new Context with raw promises -```javascript -// define a new context request -const request = { +#### SDK Options + +| Option | Type | Required | Description | +|---|---|---|---| +| `endpoint` | `string` | Yes | A/B Smartly API endpoint | +| `apiKey` | `string` | Yes | API key from the web console | +| `environment` | `string` | Yes | Environment name (e.g. `"production"`) | +| `application` | `string \| { name, version }` | Yes | Application name or object | +| `agent` | `string` | No | Custom agent identifier | +| `retries` | `number` | No | Number of retries (default: `5`) | +| `timeout` | `number` | No | Request timeout in ms (default: `3000`) | +| `keepalive` | `boolean` | No | Enable keep-alive (default: `true`) | +| `fetchImpl` | `typeof fetch` | No | Custom fetch implementation for legacy environments | +| `AbortControllerImpl` | `typeof AbortController` | No | Custom AbortController for legacy environments | + +#### Creating a new Context with promises +```typescript +const context = sdk.createContext({ units: { - session_id: '5ebf06d8cb5d8137290c4abb64155584fbdb64d8', + session_id: "5ebf06d8cb5d8137290c4abb64155584fbdb64d8", }, -}; - -// create context with raw promises -const context = sdk.createContext(request); +}); -context.ready().then((response) => { - console.log("ABSmartly Context ready!") +context.ready().then(() => { + console.log("ABSmartly Context ready!"); }).catch((error) => { console.log(error); }); ``` #### Creating a new Context with async/await -```javascript -// define a new context request -const request = { +```typescript +const context = sdk.createContext({ units: { - session_id: '5ebf06d8cb5d8137290c4abb64155584fbdb64d8', + session_id: "5ebf06d8cb5d8137290c4abb64155584fbdb64d8", }, -}; - -// create context with raw promises -const context = sdk.createContext(request); +}); try { await context.ready(); - console.log("ABSmartly Context ready!") + console.log("ABSmartly Context ready!"); } catch (error) { console.log(error); } @@ -109,17 +168,14 @@ We can avoid repeating the round-trip on the client-side by sending the server-s Then we can initialize the A/B Smartly context on the client-side directly with it. ```html - - - + + + ``` #### Setting extra units for a context @@ -128,8 +184,8 @@ This method may be used for example, when a user logs in to your application, an Please note that **you cannot override an already set unit type** as that would be a change of identity, and will throw an exception. In this case, you must create a new context instead. The `unit()` and `units()` methods can be called before the context is ready. -```javascript -context.unit('db_user_id', 1000013); +```typescript +context.unit("db_user_id", 1000013); // or context.units({ @@ -139,11 +195,11 @@ context.units({ #### Setting context attributes The `attribute()` and `attributes()` methods can be called before the context is ready. -```javascript -context.attribute('user_agent', navigator.userAgent); +```typescript +context.attribute("user_agent", navigator.userAgent); context.attributes({ - customer_age: 'new_customer', + customer_age: "new_customer", }); ``` @@ -151,7 +207,7 @@ context.attributes({ You can opt in to automatically include system attributes (SDK name, SDK version, application, environment, and application version) in every publish payload. These are sent as context attributes and can be useful for debugging and filtering in the Web Console. To enable this, set the `includeSystemAttributes` option to `true` when creating the context: -```javascript +```typescript const context = sdk.createContext(request, { includeSystemAttributes: true, }); @@ -160,9 +216,9 @@ const context = sdk.createContext(request, { When enabled, the following attributes are automatically prepended to the publish request payload: | Attribute | Description | -|:--- |---| +|:---|---| | `sdk_name` | The SDK agent name (e.g. `"absmartly-javascript-sdk"`) | -| `sdk_version` | The SDK version (e.g. `"1.13.4"`) | +| `sdk_version` | The SDK version (e.g. `"2.0.0"`) | | `application` | The application name from the SDK configuration | | `environment` | The environment from the SDK configuration | | `app_version` | The application version, only included if greater than `0` | @@ -170,8 +226,8 @@ When enabled, the following attributes are automatically prepended to the publis These system attributes are prepended before any user-defined attributes. #### Selecting a treatment -```javascript -if (context.treament("exp_test_experiment") == 0) { +```typescript +if (context.treatment("exp_test_experiment") === 0) { // user is in control group (variant 0) } else { // user is in treatment group @@ -180,7 +236,7 @@ if (context.treament("exp_test_experiment") == 0) { #### Tracking a goal achievement Goals are created in the A/B Smartly web console. -```javascript +```typescript context.track("payment", { item_count: 1, total_amount: 1999.99 }); ``` @@ -188,18 +244,16 @@ context.track("payment", { item_count: 1, total_amount: 1999.99 }); Sometimes it is necessary to ensure all events have been published to the A/B Smartly collector, before proceeding. One such case is when the user is about to navigate away right before being exposed to a treatment. You can explicitly call the `publish()` method, which returns a promise, before navigating away. -```javascript -await context.publish().then(() => { - window.location = "https://www.absmartly.com" -}) +```typescript +await context.publish(); +window.location = "https://www.absmartly.com"; ``` #### Finalizing The `finalize()` method will ensure all events have been published to the A/B Smartly collector, like `publish()`, and will also "seal" the context, throwing an error if any method that could generate an event is called. -```javascript -await context.finalize().then(() => { - window.location = "https://www.absmartly.com" -}) +```typescript +await context.finalize(); +window.location = "https://www.absmartly.com"; ``` #### Refreshing the context with fresh experiment data @@ -207,25 +261,20 @@ For long-running single-page-applications (SPA), the context is usually created However, any experiments being tracked in your production code, but started after the context was created, will not be triggered. To mitigate this, we can use the `refreshInterval` option when creating the context. -```javascript -const request = { - units: { - session_id: '5ebf06d8cb5d8137290c4abb64155584fbdb64d8', - }, -}; - -const context = sdk.createContext(request, { - refreshInterval: 5 * 60 * 1000 -}); +```typescript +const context = sdk.createContext( + { units: { session_id: "5ebf06d8cb5d8137290c4abb64155584fbdb64d8" } }, + { refreshInterval: 5 * 60 * 1000 }, +); ``` Alternatively, the `refresh()` method can be called manually. The `refresh()` method pulls updated experiment data from the A/B Smartly collector and will trigger recently started experiments when `treatment()` is called again. -```javascript +```typescript setTimeout(async () => { try { - context.refresh(); - } catch(error) { + await context.refresh(); + } catch (error) { console.error(error); } }, 5 * 60 * 1000); @@ -235,14 +284,14 @@ setTimeout(async () => { The A/B Smartly SDK can be instantiated with an event logger used for all contexts. In addition, an event logger can be specified when creating a particular context, in the `createContext` call options. The example below illustrates this with the implementation of the default event logger, used if none is specified. -```javascript -const sdk = new absmartly.SDK({ - endpoint: 'https://sandbox-api.absmartly.com/v1', +```typescript +const sdk = new SDK({ + endpoint: "https://sandbox-api.absmartly.com/v1", apiKey: process.env.ABSMARTLY_API_KEY, environment: process.env.NODE_ENV, application: process.env.APPLICATION_NAME, eventLogger: (context, eventName, data) => { - if (eventName == "error") { + if (eventName === "error") { console.error(data); } }, @@ -253,7 +302,7 @@ The data parameter depends on the type of event. Currently, the SDK logs the following events: | eventName | when | data | -|:---: |---|---| +|:---:|---|---| | `"error"` | `Context` receives an error | error object thrown | | `"ready"` | `Context` turns ready | data used to initialize the context | | `"refresh"` | `Context.refresh()` method succeeds | data used to refresh the context | @@ -267,8 +316,8 @@ Currently, the SDK logs the following events: Although generally not recommended, it is sometimes necessary to peek at a treatment without triggering an exposure. The A/B Smartly SDK provides a `peek()` method for that. -```javascript -if (context.peek("exp_test_experiment") == 0) { +```typescript +if (context.peek("exp_test_experiment") === 0) { // user is in control group (variant 0) } else { // user is in treatment group @@ -278,12 +327,12 @@ if (context.peek("exp_test_experiment") == 0) { #### Overriding treatment variants During development, for example, it is useful to force a treatment for an experiment. This can be achieved with the `override()` and/or `overrides()` methods. The `override()` and `overrides()` methods can be called before the context is ready. -```javascript - context.override("exp_test_experiment", 1); // force variant 1 of treatment - context.overrides({ - exp_test_experiment: 1, - exp_another_experiment: 0, - }); +```typescript +context.override("exp_test_experiment", 1); // force variant 1 of treatment +context.overrides({ + exp_test_experiment: 1, + exp_another_experiment: 0, +}); ``` #### HTTP request timeout @@ -291,25 +340,23 @@ It is possible to set a timeout per individual HTTP request, overriding the glob Here is an example of setting a timeout only for the createContext request. -```javascript +```typescript const context = sdk.createContext(request, { - refreshInterval: 5 * 60 * 1000 + refreshInterval: 5 * 60 * 1000, }, { - timeout: 1500 + timeout: 1500, }); ``` #### HTTP Request cancellation -Sometimes it is useful to cancel an inflight HTTP request, for example, when the user is navigating away. The A/B Smartly SDK also supports a cancellation via an `AbortSignal`. An implementation of AbortController is provided for older platforms, but will use the native implementation where available. - -Here is an example of a cancellation scenario. +Sometimes it is useful to cancel an inflight HTTP request, for example, when the user is navigating away. The A/B Smartly SDK supports cancellation via an `AbortSignal`. -```javascript -const controller = new absmartly.AbortController(); +```typescript +const controller = new AbortController(); const context = sdk.createContext(request, { - refreshInterval: 5 * 60 * 1000 + refreshInterval: 5 * 60 * 1000, }, { - signal: controller.signal + signal: controller.signal, }); // abort request if not ready after 1500ms @@ -320,6 +367,21 @@ await context.ready(); clearTimeout(timeoutId); ``` +## Migration from v1 + +### Breaking changes +- **Named exports** instead of default export: `import { SDK } from "@absmartly/javascript-sdk"` instead of `import absmartly from "@absmartly/javascript-sdk"` +- **Node.js 14+** minimum (was Node.js 6+) +- **IE10 no longer supported** - IE11+ is supported via the legacy build +- **No bundled polyfills** - `core-js`, `node-fetch`, and `rfdc` are no longer bundled. Legacy environments must provide polyfills explicitly. +- **Browser bundle renamed** - `dist/absmartly.min.js` is now `dist/index.global.js` (modern) or `dist/index.legacy.js` (IE11+) + +### New features +- Full TypeScript support with type declarations +- Zero runtime dependencies +- Optional polyfill injection (`fetchImpl`, `AbortControllerImpl`) +- ESM, CJS, and IIFE builds from a single source +- Smaller bundle size ## About A/B Smartly **A/B Smartly** is the leading provider of state-of-the-art, on-premises, full-stack experimentation platforms for engineering and product teams that want to confidently deploy features as fast as they can develop them. diff --git a/package.json b/package.json index c3cb6a5..22a304d 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ } }, "engines": { - "node": ">=18" + "node": ">=14" }, "scripts": { "build": "npm run generate-version && tsup", diff --git a/src/__tests__/client.test.ts b/src/__tests__/client.test.ts index 161c2cd..09cd59a 100644 --- a/src/__tests__/client.test.ts +++ b/src/__tests__/client.test.ts @@ -17,6 +17,31 @@ describe("Client", () => { expect(() => new Client(opts)).toThrow("Missing 'apiKey' in options argument"); }); + test("uses injected fetch implementation", async () => { + const fetchImpl = vi.fn().mockResolvedValue({ ok: true, json: () => Promise.resolve({ experiments: [] }) }); + const client = new Client({ ...defaultOpts, retries: 0, timeout: 1000, fetchImpl }); + await client.getContext(); + expect(fetchImpl).toHaveBeenCalledTimes(1); + }); + + test("uses injected AbortController implementation", async () => { + const abort = vi.fn(); + class FakeAbortController { + signal = { addEventListener: vi.fn(), removeEventListener: vi.fn() } as unknown as AbortSignal; + abort = abort; + } + const fetchImpl = vi.fn().mockResolvedValue({ ok: true, json: () => Promise.resolve({ experiments: [] }) }); + const client = new Client({ + ...defaultOpts, + retries: 0, + timeout: 1000, + fetchImpl, + AbortControllerImpl: FakeAbortController as unknown as typeof AbortController, + }); + await client.getContext(); + expect(fetchImpl).toHaveBeenCalledTimes(1); + }); + test("throws for missing endpoint", () => { const opts = { ...defaultOpts, endpoint: undefined } as unknown as ClientOptions; expect(() => new Client(opts)).toThrow("Missing 'endpoint' in options argument"); diff --git a/src/client.ts b/src/client.ts index d3b6072..ea79631 100644 --- a/src/client.ts +++ b/src/client.ts @@ -12,6 +12,8 @@ import type { export class Client { private readonly _opts: NormalizedClientOptions; private readonly _delay: number; + private readonly _fetchImpl: typeof fetch; + private readonly _AbortControllerImpl: typeof AbortController; constructor(opts: ClientOptions) { const merged: Record = Object.assign( @@ -39,8 +41,11 @@ export class Client { this._opts = merged as unknown as NormalizedClientOptions; this._delay = 50; + this._fetchImpl = (opts.fetchImpl ?? globalThis.fetch).bind(globalThis) as typeof fetch; + this._AbortControllerImpl = opts.AbortControllerImpl ?? AbortController; } + getContext(options?: Partial): Promise { return this.getUnauthed({ ...options, @@ -89,7 +94,7 @@ export class Client { } } - const controller = new AbortController(); + const controller = new this._AbortControllerImpl(); const tryOnce = (): Promise => { const opts: RequestInit = { @@ -110,7 +115,7 @@ export class Client { }; } - return fetch(url, opts).then((response: Response) => { + return this._fetchImpl(url, opts).then((response: Response) => { if (!response.ok) { const bail = response.status >= 400 && response.status < 500; return response.text().then((text: string) => { diff --git a/src/config.ts b/src/config.ts index 727780b..11d844d 100644 --- a/src/config.ts +++ b/src/config.ts @@ -5,8 +5,24 @@ interface ConfigContext { variableValue(key: string, defaultValue: unknown): unknown; } +function deepClone(value: T): T { + if (Array.isArray(value)) { + return value.map((item) => deepClone(item)) as T; + } + + if (value !== null && typeof value === "object" && Object.getPrototypeOf(value) === Object.prototype) { + const result: Record = {}; + for (const [key, item] of Object.entries(value)) { + result[key] = deepClone(item); + } + return result as T; + } + + return value; +} + export function mergeConfig(context: ConfigContext, previousConfig: Record): Record { - const merged = structuredClone(previousConfig); + const merged = deepClone(previousConfig); const keys = context.variableKeys(); for (const [variableKey, experimentName] of Object.entries(keys)) { diff --git a/src/types.ts b/src/types.ts index 12fbdb6..0639319 100644 --- a/src/types.ts +++ b/src/types.ts @@ -123,6 +123,8 @@ export type ClientOptions = { retries?: number; timeout?: number; keepalive?: boolean; + fetchImpl?: typeof fetch; + AbortControllerImpl?: typeof AbortController; }; export type NormalizedClientOptions = Omit, "application"> & { diff --git a/tsup.config.ts b/tsup.config.ts index adcd306..a8d5196 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -1,16 +1,29 @@ import { defineConfig } from "tsup"; -export default defineConfig({ - entry: ["src/index.ts"], - format: ["esm", "cjs", "iife"], - globalName: "absmartly", - dts: true, - sourcemap: true, - clean: true, - minify: true, - target: "es2022", - outExtension({ format }) { - if (format === "iife") return { js: ".global.js" }; - return {}; - }, -}); +export default defineConfig([ + { + entry: ["src/index.ts"], + format: ["esm", "cjs", "iife"], + globalName: "absmartly", + dts: true, + sourcemap: true, + clean: true, + minify: true, + target: "es2022", + outExtension({ format }) { + if (format === "iife") return { js: ".global.js" }; + return {}; + }, + }, + { + entry: ["src/index.ts"], + format: ["iife"], + globalName: "absmartly", + sourcemap: true, + minify: true, + target: "es2015", + outExtension() { + return { js: ".legacy.js" }; + }, + }, +]); From 7591421a829f7d7a4f89246766882473cbc25c52 Mon Sep 17 00:00:00 2001 From: Jonas Alves Date: Mon, 30 Mar 2026 21:28:15 -0300 Subject: [PATCH 20/22] fix: forward fetchImpl/AbortControllerImpl through SDK, correct README legacy claims, document removed AbortController export --- README.md | 20 ++++++++++++++------ src/__tests__/sdk.test.ts | 24 ++++++++++++++++++++++++ src/sdk.ts | 2 ++ 3 files changed, 40 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 552b092..c9c5f6b 100644 --- a/README.md +++ b/README.md @@ -27,9 +27,12 @@ const sdk = new SDK({ }); ``` -### Legacy Browsers (IE11+, old Safari, old Android) +### Legacy Browsers A pre-built legacy bundle transpiled to ES2015 is available at `dist/index.legacy.js`. -You must provide polyfills for missing APIs: + +**Important:** This is an ES2015 build, not an ES5 build. It can help with older browsers that support ES2015, but it does **not** restore IE11 or IE10 compatibility on its own. + +If you need legacy browser support, you must provide polyfills for missing APIs: | API | Polyfill | |---|---| @@ -54,7 +57,7 @@ You must provide polyfills for missing APIs: | `dist/index.js` | ES2022 ESM | Modern bundlers (Vite, webpack, Rollup) | | `dist/index.cjs` | ES2022 CJS | Node.js `require()` | | `dist/index.global.js` | ES2022 IIFE | Modern browsers via ` ``` -#### Directly in the browser (legacy / IE11+) +#### Directly in the browser (legacy / ES2015-capable browsers) ```html @@ -372,9 +375,9 @@ clearTimeout(timeoutId); ### Breaking changes - **Named exports** instead of default export: `import { SDK } from "@absmartly/javascript-sdk"` instead of `import absmartly from "@absmartly/javascript-sdk"` - **Node.js 14+** minimum (was Node.js 6+) -- **IE10 no longer supported** - IE11+ is supported via the legacy build +- **IE10 and IE11 are not supported by the shipped bundles** - `index.legacy.js` is ES2015, not ES5. Supporting IE10/IE11 would require an additional ES5 build plus polyfills. - **No bundled polyfills** - `core-js`, `node-fetch`, and `rfdc` are no longer bundled. Legacy environments must provide polyfills explicitly. -- **Browser bundle renamed** - `dist/absmartly.min.js` is now `dist/index.global.js` (modern) or `dist/index.legacy.js` (IE11+) +- **Browser bundle renamed** - `dist/absmartly.min.js` is now `dist/index.global.js` (modern) or `dist/index.legacy.js` (ES2015 legacy build) ### New features - Full TypeScript support with type declarations @@ -383,6 +386,11 @@ clearTimeout(timeoutId); - ESM, CJS, and IIFE builds from a single source - Smaller bundle size +### Removed exports +- `AbortController` is no longer exported by the SDK package. +- Use the platform/global `AbortController` instead. +- In legacy environments, provide your own polyfill and pass it via `AbortControllerImpl`. + ## About A/B Smartly **A/B Smartly** is the leading provider of state-of-the-art, on-premises, full-stack experimentation platforms for engineering and product teams that want to confidently deploy features as fast as they can develop them. A/B Smartly's real-time analytics helps engineering and product teams ensure that new features will improve the customer experience without breaking or degrading performance and/or business metrics. diff --git a/src/__tests__/sdk.test.ts b/src/__tests__/sdk.test.ts index d1cba1d..5bb21ca 100644 --- a/src/__tests__/sdk.test.ts +++ b/src/__tests__/sdk.test.ts @@ -81,4 +81,28 @@ describe("SDK", () => { sdk.setContextDataProvider(provider as never); expect(sdk.getContextDataProvider()).toBe(provider); }); + + test("forwards fetchImpl and AbortControllerImpl to Client", async () => { + const fetchImpl = vi.fn().mockResolvedValue({ ok: true, json: () => Promise.resolve({ experiments: [] }) }); + let abortControllerInstances = 0; + class FakeAbortController { + signal = { addEventListener: vi.fn(), removeEventListener: vi.fn() } as unknown as AbortSignal; + constructor() { + abortControllerInstances += 1; + } + abort = vi.fn(); + } + + const sdk = new SDK({ + ...defaultOpts, + fetchImpl, + AbortControllerImpl: FakeAbortController as unknown as typeof AbortController, + }); + const context = sdk.createContext({ units: { session_id: "abc" } }); + await context.ready(); + + expect(fetchImpl).toHaveBeenCalledTimes(1); + expect(abortControllerInstances).toBe(1); + }); }); + diff --git a/src/sdk.ts b/src/sdk.ts index 6a6a631..7831e74 100644 --- a/src/sdk.ts +++ b/src/sdk.ts @@ -46,6 +46,8 @@ const CLIENT_OPTION_KEYS = [ "environment", "retries", "timeout", + "fetchImpl", + "AbortControllerImpl", ]; export class SDK { From 1387ff4697e6ce3c99aba6114f565db9ee9c4393 Mon Sep 17 00:00:00 2001 From: Jonas Alves Date: Fri, 10 Apr 2026 18:15:56 +0100 Subject: [PATCH 21/22] =?UTF-8?q?feat:=20merge=20best=20of=20both=20TS=20r?= =?UTF-8?q?ewrites=20=E2=80=94=20interface-based=20DI,=20bug=20fixes,=20co?= =?UTF-8?q?mprehensive=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Merge improvements from both TypeScript SDK rewrites into a single codebase, fixing critical bugs and adding 118 new tests (424 total). Types & Architecture: - Split types.ts into models.ts (wire-format) and interfaces.ts (DI contracts) - Add Client, ContextDataProvider, ContextPublisher interfaces with Default* implementations - Add domain error hierarchy: ABSmartlyError, ContextNotReadyError, ContextFinalizedError - Stronger Assignment type: required fields with null instead of optional - Add ExperimentVariant, ExperimentApplication, GoalAchievement named types - Add readyError(), getSDK(), getOptions() public API methods - Add input validation on all public methods matching original SDK Critical bug fixes: - _flush: snapshot-and-restore pattern prevents data loss on publish failure - Audience mismatch: null evaluation → true (safe exclude), matching original SDK - _evaluateAudience: try/catch wrapper prevents evaluator crashes - stringToUint8Array: handle UTF-16 surrogate pairs (emoji/4-byte UTF-8) - _init: isObject() guard on parsed config + error logging - Client: forward per-request timeout to retry logic Important fixes: - getUnits() returns defensive copy - _getAttributesMap() caches result until attrs change - _flush logs discard when context is failed - _logEvent/_logError wrapped in try/catch (logger can't crash SDK) - _customFieldValue uses _logError instead of console.error --- src/__tests__/client.test.ts | 47 ++-- src/__tests__/context.test.ts | 382 ++++++++++++++++++++++++++++++-- src/__tests__/errors.test.ts | 34 ++- src/__tests__/hashing.test.ts | 20 ++ src/__tests__/matcher.test.ts | 118 ++++++++-- src/__tests__/operators.test.ts | 205 +++++++++++++++++ src/__tests__/provider.test.ts | 6 +- src/__tests__/publisher.test.ts | 6 +- src/__tests__/sdk.test.ts | 21 +- src/__tests__/utils.test.ts | 16 ++ src/client.ts | 22 +- src/context.ts | 229 ++++++++++++++----- src/errors.ts | 21 ++ src/hashing.ts | 28 ++- src/index.ts | 42 +++- src/interfaces.ts | 74 +++++++ src/matcher.ts | 16 +- src/{types.ts => models.ts} | 104 +++------ src/provider.ts | 5 +- src/publisher.ts | 5 +- src/sdk.ts | 48 ++-- src/utils.ts | 2 +- 22 files changed, 1190 insertions(+), 261 deletions(-) create mode 100644 src/__tests__/operators.test.ts create mode 100644 src/interfaces.ts rename src/{types.ts => models.ts} (50%) diff --git a/src/__tests__/client.test.ts b/src/__tests__/client.test.ts index 09cd59a..6947093 100644 --- a/src/__tests__/client.test.ts +++ b/src/__tests__/client.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test, vi, beforeEach, afterEach } from "vitest"; -import { Client } from "../client"; -import type { ClientOptions } from "../types"; +import { DefaultClient } from "../client"; +import type { ClientOptions } from "../interfaces"; const defaultOpts: ClientOptions = { agent: "test-agent", @@ -14,12 +14,12 @@ describe("Client", () => { describe("constructor validation", () => { test("throws for missing apiKey", () => { const opts = { ...defaultOpts, apiKey: undefined } as unknown as ClientOptions; - expect(() => new Client(opts)).toThrow("Missing 'apiKey' in options argument"); + expect(() => new DefaultClient(opts)).toThrow("Missing 'apiKey' in options argument"); }); test("uses injected fetch implementation", async () => { const fetchImpl = vi.fn().mockResolvedValue({ ok: true, json: () => Promise.resolve({ experiments: [] }) }); - const client = new Client({ ...defaultOpts, retries: 0, timeout: 1000, fetchImpl }); + const client = new DefaultClient({ ...defaultOpts, retries: 0, timeout: 1000, fetchImpl }); await client.getContext(); expect(fetchImpl).toHaveBeenCalledTimes(1); }); @@ -31,7 +31,7 @@ describe("Client", () => { abort = abort; } const fetchImpl = vi.fn().mockResolvedValue({ ok: true, json: () => Promise.resolve({ experiments: [] }) }); - const client = new Client({ + const client = new DefaultClient({ ...defaultOpts, retries: 0, timeout: 1000, @@ -44,44 +44,44 @@ describe("Client", () => { test("throws for missing endpoint", () => { const opts = { ...defaultOpts, endpoint: undefined } as unknown as ClientOptions; - expect(() => new Client(opts)).toThrow("Missing 'endpoint' in options argument"); + expect(() => new DefaultClient(opts)).toThrow("Missing 'endpoint' in options argument"); }); test("throws for missing environment", () => { const opts = { ...defaultOpts, environment: undefined } as unknown as ClientOptions; - expect(() => new Client(opts)).toThrow("Missing 'environment' in options argument"); + expect(() => new DefaultClient(opts)).toThrow("Missing 'environment' in options argument"); }); test("throws for missing application", () => { const opts = { ...defaultOpts, application: undefined } as unknown as ClientOptions; - expect(() => new Client(opts)).toThrow("Missing 'application' in options argument"); + expect(() => new DefaultClient(opts)).toThrow("Missing 'application' in options argument"); }); test("throws for empty apiKey", () => { const opts = { ...defaultOpts, apiKey: "" }; - expect(() => new Client(opts)).toThrow("Invalid 'apiKey' in options argument"); + expect(() => new DefaultClient(opts)).toThrow("Invalid 'apiKey' in options argument"); }); test("accepts ApplicationObject", () => { const opts = { ...defaultOpts, application: { name: "my-app", version: "1.0.0" } }; - const client = new Client(opts); + const client = new DefaultClient(opts); expect(client.getApplication()).toEqual({ name: "my-app", version: "1.0.0" }); }); test("converts string application to ApplicationObject", () => { - const client = new Client(defaultOpts); + const client = new DefaultClient(defaultOpts); expect(client.getApplication()).toEqual({ name: "test-app", version: 0 }); }); }); describe("accessors", () => { test("getAgent", () => { - const client = new Client(defaultOpts); + const client = new DefaultClient(defaultOpts); expect(client.getAgent()).toBe("test-agent"); }); test("getEnvironment", () => { - const client = new Client(defaultOpts); + const client = new DefaultClient(defaultOpts); expect(client.getEnvironment()).toBe("test"); }); }); @@ -99,7 +99,7 @@ describe("Client", () => { const mockResponse = { ok: true, json: () => Promise.resolve({ experiments: [] }) }; (globalThis.fetch as ReturnType).mockResolvedValue(mockResponse); - const client = new Client({ ...defaultOpts, retries: 0, timeout: 1000 }); + const client = new DefaultClient({ ...defaultOpts, retries: 0, timeout: 1000 }); await client.getContext(); expect(globalThis.fetch).toHaveBeenCalledTimes(1); @@ -108,6 +108,17 @@ describe("Client", () => { expect(url).toContain("application=test-app"); expect(url).toContain("environment=test"); }); + + test("GET /context does NOT include auth headers", async () => { + const mockResponse = { ok: true, json: () => Promise.resolve({ experiments: [] }) }; + (globalThis.fetch as ReturnType).mockResolvedValue(mockResponse); + + const client = new DefaultClient({ ...defaultOpts, retries: 0, timeout: 1000 }); + await client.getContext(); + + const [, opts] = (globalThis.fetch as ReturnType).mock.calls[0]!; + expect(opts.headers).toBeUndefined(); + }); }); describe("publish", () => { @@ -123,7 +134,7 @@ describe("Client", () => { const mockResponse = { ok: true, json: () => Promise.resolve({}) }; (globalThis.fetch as ReturnType).mockResolvedValue(mockResponse); - const client = new Client({ ...defaultOpts, retries: 0, timeout: 1000 }); + const client = new DefaultClient({ ...defaultOpts, retries: 0, timeout: 1000 }); await client.publish({ units: [{ type: "session_id", uid: "abc" }], publishedAt: 1000, @@ -142,7 +153,7 @@ describe("Client", () => { const mockResponse = { ok: true, json: () => Promise.resolve({}) }; (globalThis.fetch as ReturnType).mockResolvedValue(mockResponse); - const client = new Client({ ...defaultOpts, retries: 0, timeout: 1000 }); + const client = new DefaultClient({ ...defaultOpts, retries: 0, timeout: 1000 }); await client.publish({ units: [{ type: "session_id", uid: "abc" }], publishedAt: 1000, @@ -178,7 +189,7 @@ describe("Client", () => { .mockResolvedValueOnce(failResponse) .mockResolvedValueOnce(successResponse); - const client = new Client({ ...defaultOpts, retries: 3, timeout: 10000 }); + const client = new DefaultClient({ ...defaultOpts, retries: 3, timeout: 10000 }); const promise = client.getContext(); await vi.runAllTimersAsync(); @@ -198,7 +209,7 @@ describe("Client", () => { (globalThis.fetch as ReturnType).mockResolvedValue(failResponse); - const client = new Client({ ...defaultOpts, retries: 3, timeout: 10000 }); + const client = new DefaultClient({ ...defaultOpts, retries: 3, timeout: 10000 }); const promise = client.getContext(); await expect(promise).rejects.toThrow("bad request"); diff --git a/src/__tests__/context.test.ts b/src/__tests__/context.test.ts index 5bd8173..4af3f1f 100644 --- a/src/__tests__/context.test.ts +++ b/src/__tests__/context.test.ts @@ -1,9 +1,10 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import { Context } from "../context"; -import { ContextPublisher } from "../publisher"; -import { ContextDataProvider } from "../provider"; +import { DefaultContextPublisher } from "../publisher"; +import { DefaultContextDataProvider } from "../provider"; import { hashUnit } from "../hashing"; import { SDK_VERSION } from "../version"; +import type { ContextPublisher, ContextDataProvider } from "../interfaces"; function clone(obj: T): T { return JSON.parse(JSON.stringify(obj)); @@ -431,6 +432,32 @@ describe("Context", () => { expect(context.publisher()).toBe(publisher); }); + it("ready() should resolve true even when context fails", async () => { + const context = new Context(sdk, contextOptions, contextParams, Promise.reject(new Error("network error")) as any); + const result = await context.ready(); + expect(result).toBe(true); + expect(context.isFailed()).toBe(true); + expect(context.isReady()).toBe(true); + }); + + it("should return control variant (0) for all experiments after failure", async () => { + const context = new Context(sdk, contextOptions, contextParams, Promise.reject(new Error("server error")) as any); + await context.ready(); + expect(context.isFailed()).toBe(true); + + expect(context.treatment("exp_test_ab")).toBe(0); + expect(context.treatment("exp_test_abc")).toBe(0); + expect(context.treatment("any_unknown_experiment")).toBe(0); + expect(context.peek("exp_test_ab")).toBe(0); + }); + + it("ready() should resolve true on success", async () => { + const context = new Context(sdk, contextOptions, contextParams, Promise.resolve(getContextResponse as any)); + const result = await context.ready(); + expect(result).toBe(true); + expect(context.isFailed()).toBe(false); + }); + it("should call event logger on error", async () => { defaultEventLogger.mockClear(); @@ -1105,7 +1132,7 @@ describe("Context", () => { variant: 0, fullOn: false, custom: false, - audienceMismatch: false, + audienceMismatch: true, }, { id: 4, @@ -1118,7 +1145,7 @@ describe("Context", () => { variant: 2, fullOn: true, custom: false, - audienceMismatch: false, + audienceMismatch: true, }, { id: 5, @@ -1175,7 +1202,7 @@ describe("Context", () => { variant: expectedVariants[experiment.name], fullOn: experiment.name === "exp_test_fullon", custom: false, - audienceMismatch: false, + audienceMismatch: experiment.name === "exp_test_not_eligible" || experiment.name === "exp_test_fullon", }); } @@ -1570,7 +1597,7 @@ describe("Context", () => { variant: 0, fullOn: false, custom: false, - audienceMismatch: false, + audienceMismatch: true, }, { id: 4, @@ -1583,7 +1610,7 @@ describe("Context", () => { variant: 2, fullOn: true, custom: false, - audienceMismatch: false, + audienceMismatch: true, }, { id: 5, @@ -1886,7 +1913,7 @@ describe("Context", () => { overridden: false, fullOn: false, custom: false, - audienceMismatch: false, + audienceMismatch: true, }, ], goals: [ @@ -2520,7 +2547,7 @@ describe("Context", () => { variant: 0, fullOn: false, custom: false, - audienceMismatch: false, + audienceMismatch: true, }, { id: 4, @@ -2533,7 +2560,7 @@ describe("Context", () => { variant: 2, fullOn: true, custom: false, - audienceMismatch: false, + audienceMismatch: true, }, ], }, @@ -2701,30 +2728,32 @@ describe("Context", () => { expect(context.customFieldValue("exp_test_custom_fields", "false_boolean_field")).toEqual(false); }); - it("should console an error when JSON cannot be parsed", () => { - const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + it("should log an error when JSON cannot be parsed", () => { const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); expect(context.pending()).toEqual(0); expect(context.customFieldValue("exp_test_abc", "json_invalid")).toEqual(null); - expect(errorSpy).toHaveBeenCalledTimes(1); - expect(errorSpy).toHaveBeenCalledWith( - "Failed to parse JSON custom field value 'json_invalid' for experiment 'exp_test_abc'" + expect(defaultEventLogger).toHaveBeenCalledWith( + context, + "error", + expect.objectContaining({ + message: expect.stringContaining("Failed to parse JSON custom field value 'json_invalid' for experiment 'exp_test_abc'") + }) ); - errorSpy.mockRestore(); }); - it("should console an error when a field type is invalid", () => { - const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + it("should log an error when a field type is invalid", () => { const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); expect(context.pending()).toEqual(0); expect(context.customFieldValue("exp_test_custom_fields", "invalid_type_field")).toEqual(null); - expect(errorSpy).toHaveBeenCalledTimes(1); - expect(errorSpy).toHaveBeenCalledWith( - "Unknown custom field type 'invalid' for experiment 'exp_test_custom_fields' and key 'invalid_type_field' - you may need to upgrade to the latest SDK version" + expect(defaultEventLogger).toHaveBeenCalledWith( + context, + "error", + expect.objectContaining({ + message: "Unknown custom field type 'invalid' for experiment 'exp_test_custom_fields' and key 'invalid_type_field' - you may need to upgrade to the latest SDK version" + }) ); - errorSpy.mockRestore(); }); }); @@ -2905,4 +2934,313 @@ describe("Context", () => { ]); }); }); + + describe("publishDelay auto-flush", () => { + beforeEach(() => { + vi.useFakeTimers({ shouldAdvanceTime: false }); + vi.spyOn(Date, "now").mockImplementation(() => timeOrigin); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it("should auto-flush after publishDelay ms when treatment is called", async () => { + const options = { publishDelay: 100, refreshPeriod: 0 }; + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + const context = new Context(sdk, options, contextParams, getContextResponse as any); + context.treatment("exp_test_ab"); + expect(publisher.publish).not.toHaveBeenCalled(); + + await vi.advanceTimersByTimeAsync(100); + expect(publisher.publish).toHaveBeenCalledTimes(1); + }); + + it("should auto-flush after publishDelay ms when track is called", async () => { + const options = { publishDelay: 100, refreshPeriod: 0 }; + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + const context = new Context(sdk, options, contextParams, getContextResponse as any); + context.track("goal1", { amount: 100 }); + expect(publisher.publish).not.toHaveBeenCalled(); + + await vi.advanceTimersByTimeAsync(100); + expect(publisher.publish).toHaveBeenCalledTimes(1); + }); + + it("should not create multiple timers for multiple queued events", async () => { + const options = { publishDelay: 100, refreshPeriod: 0 }; + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + const context = new Context(sdk, options, contextParams, getContextResponse as any); + context.treatment("exp_test_ab"); + context.treatment("exp_test_abc"); + context.track("goal1"); + + await vi.advanceTimersByTimeAsync(100); + expect(publisher.publish).toHaveBeenCalledTimes(1); + }); + + it("should start auto-flush timer after ready if events queued before ready", async () => { + const options = { publishDelay: 100, refreshPeriod: 0 }; + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + + let resolveData: (data: any) => void; + const dataPromise = new Promise((resolve) => { resolveData = resolve; }); + + const context = new Context(sdk, options, contextParams, dataPromise); + expect(context.isReady()).toBe(false); + + // Resolve the promise to make context ready + resolveData!(getContextResponse); + await context.ready(); + expect(context.isReady()).toBe(true); + + // Queue events after ready + context.treatment("exp_test_ab"); + + // Timer should fire after publishDelay + await vi.advanceTimersByTimeAsync(100); + expect(publisher.publish).toHaveBeenCalledTimes(1); + }); + }); + + describe("refreshPeriod auto-refresh", () => { + beforeEach(() => { + vi.useFakeTimers({ shouldAdvanceTime: false }); + vi.spyOn(Date, "now").mockImplementation(() => timeOrigin); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it("should start refresh timer when refreshPeriod > 0", async () => { + const options = { publishDelay: -1, refreshPeriod: 1000 }; + (provider.getContextData as ReturnType).mockReturnValue(Promise.resolve(getContextResponse)); + + const context = new Context(sdk, options, contextParams, getContextResponse as any); + expect(context.isReady()).toBe(true); + + await vi.advanceTimersByTimeAsync(1000); + expect(provider.getContextData).toHaveBeenCalledTimes(1); + }); + + it("should call refresh multiple times on interval", async () => { + const options = { publishDelay: -1, refreshPeriod: 1000 }; + (provider.getContextData as ReturnType).mockReturnValue(Promise.resolve(getContextResponse)); + + const context = new Context(sdk, options, contextParams, getContextResponse as any); + + await vi.advanceTimersByTimeAsync(3000); + expect(provider.getContextData).toHaveBeenCalledTimes(3); + }); + + it("should stop refresh timer on finalize", async () => { + const options = { publishDelay: -1, refreshPeriod: 1000 }; + (provider.getContextData as ReturnType).mockReturnValue(Promise.resolve(getContextResponse)); + + const context = new Context(sdk, options, contextParams, getContextResponse as any); + + await context.finalize(); + + await vi.advanceTimersByTimeAsync(3000); + expect(provider.getContextData).not.toHaveBeenCalled(); + }); + }); + + describe("event logger resilience", () => { + it("should not crash when event logger throws on treatment", () => { + const throwingLogger = vi.fn().mockImplementation(() => { throw new Error("logger broken"); }); + const context = new Context(sdk, { ...contextOptions, eventLogger: throwingLogger }, contextParams, getContextResponse as any); + expect(() => context.treatment("exp_test_ab")).not.toThrow(); + }); + + it("should not crash when event logger throws on track", () => { + const throwingLogger = vi.fn().mockImplementation(() => { throw new Error("logger broken"); }); + const context = new Context(sdk, { ...contextOptions, eventLogger: throwingLogger }, contextParams, getContextResponse as any); + expect(() => context.track("goal1", { amount: 1 })).not.toThrow(); + }); + + it("should not crash when event logger throws on publish", async () => { + const throwingLogger = vi.fn().mockImplementation(() => { throw new Error("logger broken"); }); + (publisher.publish as ReturnType).mockReturnValue(Promise.resolve()); + const context = new Context(sdk, { ...contextOptions, eventLogger: throwingLogger }, contextParams, getContextResponse as any); + context.treatment("exp_test_ab"); + await expect(context.publish()).resolves.toBeUndefined(); + }); + }); + + describe("flush data preservation on failure", () => { + it("should preserve events on publish failure and retry", async () => { + (publisher.publish as ReturnType) + .mockReturnValueOnce(Promise.reject(new Error("network error"))) + .mockReturnValueOnce(Promise.resolve()); + + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + context.treatment("exp_test_ab"); + + // First publish fails + await expect(context.publish()).rejects.toThrow("network error"); + + // Events should still be pending + expect(context.pending()).toBe(1); + + // Second publish succeeds with the preserved events + await context.publish(); + expect(context.pending()).toBe(0); + expect(publisher.publish).toHaveBeenCalledTimes(2); + }); + + it("should not lose new events queued during in-flight publish", async () => { + let resolvePublish: () => void; + (publisher.publish as ReturnType).mockReturnValue(new Promise((resolve) => { resolvePublish = resolve; })); + + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + context.treatment("exp_test_ab"); + + const publishPromise = context.publish(); + + // Queue new event while publish is in-flight + context.track("goal1"); + expect(context.pending()).toBe(1); // new event pending + + // Resolve the first publish + resolvePublish!(); + await publishPromise; + + // New event should still be pending + expect(context.pending()).toBe(1); + }); + }); + + describe("override cache invalidation", () => { + it("should queue new exposure when override value changes", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + context.override("exp_test_ab", 2); + context.treatment("exp_test_ab"); + expect(context.pending()).toBe(1); + + // Same override value — no new exposure + context.override("exp_test_ab", 2); + context.treatment("exp_test_ab"); + expect(context.pending()).toBe(1); + + // Different override value — new exposure + context.override("exp_test_ab", 3); + context.treatment("exp_test_ab"); + expect(context.pending()).toBe(2); + }); + }); + + describe("input validation", () => { + it("treatment() should throw for empty experiment name", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(() => context.treatment("")).toThrow("Experiment name must be a non-empty string"); + }); + + it("peek() should throw for empty experiment name", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(() => context.peek("")).toThrow("Experiment name must be a non-empty string"); + }); + + it("track() should throw for empty goal name", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(() => context.track("")).toThrow("Goal name must be a non-empty string"); + }); + + it("attribute() should throw for empty attribute name", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(() => context.attribute("", "value")).toThrow("Attribute name must be a non-empty string"); + }); + + it("variableValue() should throw for empty key", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(() => context.variableValue("", "default")).toThrow("Variable key must be a non-empty string"); + }); + + it("override() should throw for empty experiment name", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(() => context.override("", 1)).toThrow("Experiment name must be a non-empty string"); + }); + + it("override() should throw for negative variant", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(() => context.override("exp_test_ab", -1)).toThrow("Variant must be a non-negative integer"); + }); + + it("override() should throw for non-integer variant", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(() => context.override("exp_test_ab", 1.5)).toThrow("Variant must be a non-negative integer"); + }); + + it("customAssignment() should throw for empty experiment name", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(() => context.customAssignment("", 1)).toThrow("Experiment name must be a non-empty string"); + }); + + it("customAssignment() should throw for negative variant", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(() => context.customAssignment("exp_test_ab", -1)).toThrow("Variant must be a non-negative integer"); + }); + + it("customFieldValue() should throw for empty experiment name", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(() => context.customFieldValue("", "key")).toThrow("Experiment name must be a non-empty string"); + }); + + it("customFieldValue() should throw for empty key", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(() => context.customFieldValue("exp_test_ab", "")).toThrow("Key must be a non-empty string"); + }); + }); + + describe("readyError()", () => { + it("should return null when context succeeds", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.readyError()).toBe(null); + }); + + it("should return the error when context fails", async () => { + const error = new Error("network failure"); + const context = new Context(sdk, contextOptions, contextParams, Promise.reject(error) as any); + await context.ready(); + expect(context.readyError()).toBe(error); + }); + }); + + describe("getSDK() and getOptions()", () => { + it("getSDK() should return the sdk", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + expect(context.getSDK()).toBe(sdk); + }); + + it("getOptions() should return a copy of options", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + const options = context.getOptions(); + expect(options.publishDelay).toBe(contextOptions.publishDelay); + expect(options.refreshPeriod).toBe(contextOptions.refreshPeriod); + expect(options).not.toBe(contextOptions); // should be a copy + }); + }); + + describe("getUnits() defensive copy", () => { + it("should return a copy that does not mutate internal state", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + const units = context.getUnits(); + units["hacked"] = "value"; + expect(context.getUnit("hacked")).toBeUndefined(); + }); + }); + + describe("peek then treatment", () => { + it("should queue exposure on treatment after peek", () => { + const context = new Context(sdk, contextOptions, contextParams, getContextResponse as any); + context.peek("exp_test_ab"); + expect(context.pending()).toBe(0); + context.treatment("exp_test_ab"); + expect(context.pending()).toBe(1); + }); + }); }); diff --git a/src/__tests__/errors.test.ts b/src/__tests__/errors.test.ts index e9b009e..1d74064 100644 --- a/src/__tests__/errors.test.ts +++ b/src/__tests__/errors.test.ts @@ -1,5 +1,37 @@ import { describe, expect, test } from "vitest"; -import { AbortError, RetryError, TimeoutError } from "../errors"; +import { ABSmartlyError, AbortError, ContextFinalizedError, ContextNotReadyError, RetryError, TimeoutError } from "../errors"; + +describe("ABSmartlyError", () => { + test("has correct name and message", () => { + const error = new ABSmartlyError("test message"); + expect(error).toBeInstanceOf(Error); + expect(error).toBeInstanceOf(ABSmartlyError); + expect(error.name).toBe("ABSmartlyError"); + expect(error.message).toBe("test message"); + }); +}); + +describe("ContextNotReadyError", () => { + test("has correct name, message, and extends ABSmartlyError", () => { + const error = new ContextNotReadyError(); + expect(error).toBeInstanceOf(Error); + expect(error).toBeInstanceOf(ABSmartlyError); + expect(error).toBeInstanceOf(ContextNotReadyError); + expect(error.name).toBe("ContextNotReadyError"); + expect(error.message).toBe("Context is not yet ready"); + }); +}); + +describe("ContextFinalizedError", () => { + test("has correct name, message, and extends ABSmartlyError", () => { + const error = new ContextFinalizedError(); + expect(error).toBeInstanceOf(Error); + expect(error).toBeInstanceOf(ABSmartlyError); + expect(error).toBeInstanceOf(ContextFinalizedError); + expect(error.name).toBe("ContextFinalizedError"); + expect(error.message).toBe("Context has been finalized"); + }); +}); describe("TimeoutError", () => { test("has correct name, message, and timeout", () => { diff --git a/src/__tests__/hashing.test.ts b/src/__tests__/hashing.test.ts index f1038cc..f312c4f 100644 --- a/src/__tests__/hashing.test.ts +++ b/src/__tests__/hashing.test.ts @@ -16,6 +16,12 @@ describe("stringToUint8Array", () => { const result = stringToUint8Array(""); expect(result.length).toBe(0); }); + + test("encodes 4-byte characters (emoji/surrogate pairs)", () => { + const result = stringToUint8Array("\u{1F600}"); + // U+1F600 = F0 9F 98 80 in UTF-8 + expect(Array.from(result)).toEqual([0xf0, 0x9f, 0x98, 0x80]); + }); }); describe("base64UrlNoPadding", () => { @@ -34,6 +40,13 @@ describe("base64UrlNoPadding", () => { test("encodes 3 bytes", () => { expect(base64UrlNoPadding(new Uint8Array([0, 0, 0]))).toBe("AAAA"); }); + + test("uses URL-safe characters (no +, /, =)", () => { + const result = base64UrlNoPadding(new Uint8Array([255, 254, 253, 252, 251, 250])); + expect(result).not.toContain("+"); + expect(result).not.toContain("/"); + expect(result).not.toContain("="); + }); }); describe("hashUnit", () => { @@ -57,4 +70,11 @@ describe("hashUnit", () => { test("produces different results for different inputs", () => { expect(hashUnit("abc")).not.toBe(hashUnit("def")); }); + + test("returns exactly 22 chars (MD5 = 16 bytes = 22 base64url chars)", () => { + expect(hashUnit("a").length).toBe(22); + expect(hashUnit("abcdefghijklmnopqrstuvwxyz").length).toBe(22); + expect(hashUnit("bleh@absmartly.com").length).toBe(22); + expect(hashUnit(123456789).length).toBe(22); + }); }); diff --git a/src/__tests__/matcher.test.ts b/src/__tests__/matcher.test.ts index f010e2a..0664f7a 100644 --- a/src/__tests__/matcher.test.ts +++ b/src/__tests__/matcher.test.ts @@ -1,33 +1,111 @@ -import { describe, expect, test } from "vitest"; +import { describe, expect, test, it } from "vitest"; import { AudienceMatcher } from "../matcher"; describe("AudienceMatcher", () => { const matcher = new AudienceMatcher(); - test("evaluates matching audience", () => { - const audience = JSON.stringify({ filter: [{ value: true }] }); - expect(matcher.evaluate(audience, {})).toBe(true); - }); + describe("null/empty handling", () => { + test("null audience → null", () => { + expect(matcher.evaluate(null as any, {})).toBe(null); + }); - test("evaluates non-matching audience", () => { - const audience = JSON.stringify({ filter: [{ value: false }] }); - expect(matcher.evaluate(audience, {})).toBe(false); - }); + test("empty string → null", () => { + expect(matcher.evaluate("", {})).toBe(null); + }); - test("evaluates with not operator", () => { - const audience = JSON.stringify({ filter: [{ not: { value: false } }] }); - expect(matcher.evaluate(audience, {})).toBe(true); - }); + test("returns null for invalid JSON", () => { + expect(matcher.evaluate("invalid json", {})).toBe(null); + }); - test("returns null for invalid JSON", () => { - expect(matcher.evaluate("invalid json", {})).toBe(null); - }); + test("returns null for missing filter", () => { + expect(matcher.evaluate(JSON.stringify({}), {})).toBe(null); + }); - test("returns null for missing filter", () => { - expect(matcher.evaluate(JSON.stringify({}), {})).toBe(null); + test("returns null for null filter", () => { + expect(matcher.evaluate(JSON.stringify({ filter: null }), {})).toBe(null); + }); }); - test("returns null for null filter", () => { - expect(matcher.evaluate(JSON.stringify({ filter: null }), {})).toBe(null); + describe("filter evaluation", () => { + test("evaluates matching audience", () => { + const audience = JSON.stringify({ filter: [{ value: true }] }); + expect(matcher.evaluate(audience, {})).toBe(true); + }); + + test("evaluates non-matching audience", () => { + const audience = JSON.stringify({ filter: [{ value: false }] }); + expect(matcher.evaluate(audience, {})).toBe(false); + }); + + test("evaluates with not operator", () => { + const audience = JSON.stringify({ filter: [{ not: { value: false } }] }); + expect(matcher.evaluate(audience, {})).toBe(true); + }); + + it("and with all true → true", () => { + const audience = JSON.stringify({ + filter: [{ and: [{ value: true }, { value: true }] }], + }); + expect(matcher.evaluate(audience, {})).toBe(true); + }); + + it("and with one false → false", () => { + const audience = JSON.stringify({ + filter: [{ and: [{ value: true }, { value: false }] }], + }); + expect(matcher.evaluate(audience, {})).toBe(false); + }); + + it("evaluates with attributes (gte)", () => { + const audience = JSON.stringify({ + filter: [{ gte: [{ var: "age" }, { value: 18 }] }], + }); + expect(matcher.evaluate(audience, { age: 25 })).toBe(true); + expect(matcher.evaluate(audience, { age: 15 })).toBe(false); + }); + + it("complex filter with and+gte+in", () => { + const audience = JSON.stringify({ + filter: [{ + and: [ + { gte: [{ var: "age" }, { value: 18 }] }, + { in: [{ value: ["US", "UK"] }, { var: "country" }] }, + ], + }], + }); + expect(matcher.evaluate(audience, { age: 25, country: "US" })).toBe(true); + expect(matcher.evaluate(audience, { age: 25, country: "FR" })).toBe(false); + expect(matcher.evaluate(audience, { age: 15, country: "US" })).toBe(false); + }); + + it("or filter", () => { + const audience = JSON.stringify({ + filter: [{ + or: [ + { eq: [{ var: "plan" }, { value: "pro" }] }, + { eq: [{ var: "plan" }, { value: "enterprise" }] }, + ], + }], + }); + expect(matcher.evaluate(audience, { plan: "pro" })).toBe(true); + expect(matcher.evaluate(audience, { plan: "enterprise" })).toBe(true); + expect(matcher.evaluate(audience, { plan: "free" })).toBe(false); + }); + + it("not filter", () => { + const audience = JSON.stringify({ + filter: [{ not: { eq: [{ var: "bot" }, { value: true }] } }], + }); + expect(matcher.evaluate(audience, { bot: false })).toBe(true); + expect(matcher.evaluate(audience, { bot: true })).toBe(false); + }); + + it("match filter with regex", () => { + const audience = JSON.stringify({ + filter: [{ match: [{ var: "email" }, { value: ".*@absmartly\\.com$" }] }], + }); + expect(matcher.evaluate(audience, { email: "user@absmartly.com" })).toBe(true); + expect(matcher.evaluate(audience, { email: "user@other.com" })).toBe(false); + }); }); }); diff --git a/src/__tests__/operators.test.ts b/src/__tests__/operators.test.ts new file mode 100644 index 0000000..3c2421e --- /dev/null +++ b/src/__tests__/operators.test.ts @@ -0,0 +1,205 @@ +import { describe, it, expect } from "vitest"; +import { Evaluator } from "../jsonexpr/evaluator"; +import { + AndCombinator, + EqualsOperator, + GreaterThanOperator, + GreaterThanOrEqualOperator, + InOperator, + LessThanOperator, + LessThanOrEqualOperator, + MatchOperator, + NotOperator, + NullOperator, + OrCombinator, + ValueOperator, + VarOperator, +} from "../jsonexpr/operators"; + +const operators = { + and: new AndCombinator(), + or: new OrCombinator(), + value: new ValueOperator(), + var: new VarOperator(), + null: new NullOperator(), + not: new NotOperator(), + in: new InOperator(), + match: new MatchOperator(), + eq: new EqualsOperator(), + gt: new GreaterThanOperator(), + gte: new GreaterThanOrEqualOperator(), + lt: new LessThanOperator(), + lte: new LessThanOrEqualOperator(), +}; + +function evalExpr(expr: unknown, vars: Record = {}): unknown { + const e = new Evaluator(operators, vars); + return e.evaluate(expr); +} + +describe("operators", () => { + describe("value", () => { + it("returns literal", () => expect(evalExpr({ value: 42 })).toBe(42)); + it("returns string", () => expect(evalExpr({ value: "hello" })).toBe("hello")); + it("returns null", () => expect(evalExpr({ value: null })).toBe(null)); + it("returns boolean", () => expect(evalExpr({ value: true })).toBe(true)); + }); + + // NOTE: Branch VarOperator expects path string or {path: "..."}, not {value: "..."} + describe("var", () => { + it("extracts variable by path string", () => { + expect(evalExpr({ var: "x" }, { x: 99 })).toBe(99); + }); + it("extracts nested via slash-separated path", () => { + expect(evalExpr({ var: "a/b" }, { a: { b: 5 } } as any)).toBe(5); + }); + it("missing returns null", () => { + expect(evalExpr({ var: "z" }, { x: 1 })).toBe(null); + }); + it("extracts via object with path property", () => { + expect(evalExpr({ var: { path: "x" } as unknown }, { x: 42 })).toBe(42); + }); + }); + + describe("and", () => { + it("empty → true", () => expect(evalExpr({ and: [] })).toBe(true)); + it("all true → true", () => expect(evalExpr({ and: [{ value: true }, { value: 1 }] })).toBe(true)); + it("any false → false", () => expect(evalExpr({ and: [{ value: true }, { value: false }] })).toBe(false)); + it("all false → false", () => expect(evalExpr({ and: [{ value: false }, { value: 0 }] })).toBe(false)); + }); + + // NOTE: Branch OrCombinator returns true for empty array (vacuous truth), matching AndCombinator + describe("or", () => { + it("empty → true (vacuous)", () => expect(evalExpr({ or: [] })).toBe(true)); + it("any true → true", () => expect(evalExpr({ or: [{ value: false }, { value: true }] })).toBe(true)); + it("all false → false", () => expect(evalExpr({ or: [{ value: false }, { value: 0 }] })).toBe(false)); + it("first true → true", () => expect(evalExpr({ or: [{ value: true }, { value: false }] })).toBe(true)); + }); + + describe("not", () => { + it("true → false", () => expect(evalExpr({ not: { value: true } })).toBe(false)); + it("false → true", () => expect(evalExpr({ not: { value: false } })).toBe(true)); + it("1 → false", () => expect(evalExpr({ not: { value: 1 } })).toBe(false)); + it("0 → true", () => expect(evalExpr({ not: { value: 0 } })).toBe(true)); + it("null → true", () => expect(evalExpr({ not: { value: null } })).toBe(true)); + }); + + describe("null", () => { + it("null → true", () => expect(evalExpr({ null: { value: null } })).toBe(true)); + it("0 → false", () => expect(evalExpr({ null: { value: 0 } })).toBe(false)); + it("'' → false", () => expect(evalExpr({ null: { value: "" } })).toBe(false)); + it("false → false", () => expect(evalExpr({ null: { value: false } })).toBe(false)); + }); + + // NOTE: BinaryOperator base returns null if lhs is null, so eq(null, null) → null + describe("eq", () => { + it("null == null → null (null short-circuits)", () => + expect(evalExpr({ eq: [{ value: null }, { value: null }] })).toBe(null)); + it("null == 0 → null", () => expect(evalExpr({ eq: [{ value: null }, { value: 0 }] })).toBe(null)); + it("0 == 0 → true", () => expect(evalExpr({ eq: [{ value: 0 }, { value: 0 }] })).toBe(true)); + it("1 == 0 → false", () => expect(evalExpr({ eq: [{ value: 1 }, { value: 0 }] })).toBe(false)); + it("'a' == 'a' → true", () => expect(evalExpr({ eq: [{ value: "a" }, { value: "a" }] })).toBe(true)); + it("'a' == 'b' → false", () => expect(evalExpr({ eq: [{ value: "a" }, { value: "b" }] })).toBe(false)); + it("true == true → true", () => expect(evalExpr({ eq: [{ value: true }, { value: true }] })).toBe(true)); + it("true == false → false", () => expect(evalExpr({ eq: [{ value: true }, { value: false }] })).toBe(false)); + }); + + describe("gt", () => { + it("1 > 0 → true", () => expect(evalExpr({ gt: [{ value: 1 }, { value: 0 }] })).toBe(true)); + it("0 > 1 → false", () => expect(evalExpr({ gt: [{ value: 0 }, { value: 1 }] })).toBe(false)); + it("0 > 0 → false", () => expect(evalExpr({ gt: [{ value: 0 }, { value: 0 }] })).toBe(false)); + it("null > 0 → null", () => expect(evalExpr({ gt: [{ value: null }, { value: 0 }] })).toBe(null)); + it("'b' > 'a' → true", () => expect(evalExpr({ gt: [{ value: "b" }, { value: "a" }] })).toBe(true)); + }); + + describe("gte", () => { + it("1 >= 0 → true", () => expect(evalExpr({ gte: [{ value: 1 }, { value: 0 }] })).toBe(true)); + it("0 >= 0 → true", () => expect(evalExpr({ gte: [{ value: 0 }, { value: 0 }] })).toBe(true)); + it("0 >= 1 → false", () => expect(evalExpr({ gte: [{ value: 0 }, { value: 1 }] })).toBe(false)); + }); + + describe("lt", () => { + it("0 < 1 → true", () => expect(evalExpr({ lt: [{ value: 0 }, { value: 1 }] })).toBe(true)); + it("1 < 0 → false", () => expect(evalExpr({ lt: [{ value: 1 }, { value: 0 }] })).toBe(false)); + it("0 < 0 → false", () => expect(evalExpr({ lt: [{ value: 0 }, { value: 0 }] })).toBe(false)); + }); + + describe("lte", () => { + it("0 <= 1 → true", () => expect(evalExpr({ lte: [{ value: 0 }, { value: 1 }] })).toBe(true)); + it("0 <= 0 → true", () => expect(evalExpr({ lte: [{ value: 0 }, { value: 0 }] })).toBe(true)); + it("1 <= 0 → false", () => expect(evalExpr({ lte: [{ value: 1 }, { value: 0 }] })).toBe(false)); + }); + + // NOTE: Branch InOperator signature is (haystack, needle) — first arg is the collection + describe("in", () => { + it("string contains", () => { + expect(evalExpr({ in: [{ value: "abcdef" }, { value: "bc" }] })).toBe(true); + }); + it("string not contains", () => { + expect(evalExpr({ in: [{ value: "abcdef" }, { value: "xyz" }] })).toBe(false); + }); + it("array contains", () => { + expect(evalExpr({ in: [{ value: [1, 2, 3] }, { value: 2 }] })).toBe(true); + }); + it("array not contains", () => { + expect(evalExpr({ in: [{ value: [1, 2, 3] }, { value: 4 }] })).toBe(false); + }); + it("null haystack → null", () => { + expect(evalExpr({ in: [{ value: null }, { value: "a" }] })).toBe(null); + }); + it("object key exists", () => { + expect(evalExpr({ in: [{ value: { a: 1, b: 2 } }, { value: "a" }] })).toBe(true); + }); + it("object key missing", () => { + expect(evalExpr({ in: [{ value: { a: 1, b: 2 } }, { value: "c" }] })).toBe(false); + }); + }); + + describe("match", () => { + it("matches regex", () => { + expect(evalExpr({ match: [{ value: "hello world" }, { value: "hello.*" }] })).toBe(true); + }); + it("no match", () => { + expect(evalExpr({ match: [{ value: "hello" }, { value: "^world$" }] })).toBe(false); + }); + it("null text → null", () => { + expect(evalExpr({ match: [{ value: null }, { value: "abc" }] })).toBe(null); + }); + it("null pattern → null", () => { + expect(evalExpr({ match: [{ value: "hello" }, { value: null }] })).toBe(null); + }); + it("invalid regex → null", () => { + expect(evalExpr({ match: [{ value: "hello" }, { value: "[invalid" }] })).toBe(null); + }); + }); + + describe("complex expressions", () => { + it("nested and/or", () => { + const expr = { + and: [ + { or: [{ value: true }, { value: false }] }, + { not: { value: false } }, + ], + }; + expect(evalExpr(expr)).toBe(true); + }); + + it("variable comparison", () => { + const expr = { gt: [{ var: "age" }, { value: 18 }] }; + expect(evalExpr(expr, { age: 25 })).toBe(true); + expect(evalExpr(expr, { age: 15 })).toBe(false); + }); + + it("audience-like filter", () => { + const expr = { + and: [ + { gte: [{ var: "age" }, { value: 18 }] }, + { in: [{ value: ["US", "UK", "CA"] }, { var: "country" }] }, + ], + }; + expect(evalExpr(expr, { age: 25, country: "US" })).toBe(true); + expect(evalExpr(expr, { age: 25, country: "FR" })).toBe(false); + expect(evalExpr(expr, { age: 15, country: "US" })).toBe(false); + }); + }); +}); diff --git a/src/__tests__/provider.test.ts b/src/__tests__/provider.test.ts index b530a25..26d5db5 100644 --- a/src/__tests__/provider.test.ts +++ b/src/__tests__/provider.test.ts @@ -1,12 +1,12 @@ import { describe, expect, test, vi } from "vitest"; -import { ContextDataProvider } from "../provider"; +import { DefaultContextDataProvider } from "../provider"; -describe("ContextDataProvider", () => { +describe("DefaultContextDataProvider", () => { test("delegates to sdk.getClient().getContext()", async () => { const mockGetContext = vi.fn().mockResolvedValue({ experiments: [] }); const mockSdk = { getClient: () => ({ getContext: mockGetContext }) }; - const provider = new ContextDataProvider(); + const provider = new DefaultContextDataProvider(); const result = await provider.getContextData(mockSdk, { path: "/test" }); expect(mockGetContext).toHaveBeenCalledWith({ path: "/test" }); diff --git a/src/__tests__/publisher.test.ts b/src/__tests__/publisher.test.ts index 85e8634..d755958 100644 --- a/src/__tests__/publisher.test.ts +++ b/src/__tests__/publisher.test.ts @@ -1,7 +1,7 @@ import { describe, expect, test, vi } from "vitest"; -import { ContextPublisher } from "../publisher"; +import { DefaultContextPublisher } from "../publisher"; -describe("ContextPublisher", () => { +describe("DefaultContextPublisher", () => { test("delegates to sdk.getClient().publish()", async () => { const mockPublish = vi.fn().mockResolvedValue({}); const mockSdk = { getClient: () => ({ publish: mockPublish }) }; @@ -12,7 +12,7 @@ describe("ContextPublisher", () => { sdkVersion: "2.0.0", }; - const publisher = new ContextPublisher(); + const publisher = new DefaultContextPublisher(); await publisher.publish(request, mockSdk, {}, { path: "/test" }); expect(mockPublish).toHaveBeenCalledWith(request, { path: "/test" }); diff --git a/src/__tests__/sdk.test.ts b/src/__tests__/sdk.test.ts index 5bb21ca..2ce308f 100644 --- a/src/__tests__/sdk.test.ts +++ b/src/__tests__/sdk.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test, vi } from "vitest"; import { SDK } from "../sdk"; -import type { ClientOptions } from "../types"; +import type { ClientOptions } from "../interfaces"; const defaultOpts: ClientOptions = { agent: "test-agent", @@ -82,6 +82,25 @@ describe("SDK", () => { expect(sdk.getContextDataProvider()).toBe(provider); }); + test("accepts custom client via SDKOptions", () => { + const mockClient = { + getContext: vi.fn().mockResolvedValue({ experiments: [] }), + publish: vi.fn().mockResolvedValue("ok"), + getAgent: vi.fn().mockReturnValue("custom-agent"), + getApplication: vi.fn().mockReturnValue({ name: "custom-app", version: 0 }), + getEnvironment: vi.fn().mockReturnValue("custom-env"), + }; + const sdk = new SDK({ ...defaultOpts, client: mockClient } as any); + expect(sdk.getClient()).toBe(mockClient); + }); + + test("event logger fires ready event on createContextWith", () => { + const logger = vi.fn(); + const sdk = new SDK({ ...defaultOpts, eventLogger: logger }); + const context = sdk.createContextWith({ units: { session_id: "abc" } }, { experiments: [] }); + expect(logger).toHaveBeenCalledWith(context, "ready", expect.anything()); + }); + test("forwards fetchImpl and AbortControllerImpl to Client", async () => { const fetchImpl = vi.fn().mockResolvedValue({ ok: true, json: () => Promise.resolve({ experiments: [] }) }); let abortControllerInstances = 0; diff --git a/src/__tests__/utils.test.ts b/src/__tests__/utils.test.ts index c98b29c..5af8c4f 100644 --- a/src/__tests__/utils.test.ts +++ b/src/__tests__/utils.test.ts @@ -89,6 +89,18 @@ describe("arrayEqualsShallow", () => { test("both undefined", () => { expect(arrayEqualsShallow(undefined, undefined)).toBe(true); }); + + test("both null", () => { + expect(arrayEqualsShallow(null, null)).toBe(true); + }); + + test("array vs null returns false", () => { + expect(arrayEqualsShallow([1], null)).toBe(false); + }); + + test("null vs array returns false", () => { + expect(arrayEqualsShallow(null, [1])).toBe(false); + }); }); describe("chooseVariant", () => { @@ -110,4 +122,8 @@ describe("chooseVariant", () => { test("returns last variant for probability >= 1", () => { expect(chooseVariant([0.5, 0.5], 1.0)).toBe(1); }); + + test("zero-weight first variant [0.0, 1.0] with prob=0.0 returns 1", () => { + expect(chooseVariant([0.0, 1.0], 0.0)).toBe(1); + }); }); diff --git a/src/client.ts b/src/client.ts index ea79631..c01d45e 100644 --- a/src/client.ts +++ b/src/client.ts @@ -1,15 +1,13 @@ import { AbortError, RetryError, TimeoutError } from "./errors"; -import type { - ApplicationObject, - ClientOptions, - ClientRequestOptions, - ContextData, - ContextParams, - NormalizedClientOptions, - PublishParams, -} from "./types"; - -export class Client { +import type { ApplicationObject, ContextData, PublishParams } from "./models"; +import type { ContextParams } from "./interfaces"; +import type { Client, ClientOptions, ClientRequestOptions } from "./interfaces"; + +type NormalizedClientOptions = Omit, "application"> & { + application: ApplicationObject; +}; + +export class DefaultClient implements Client { private readonly _opts: NormalizedClientOptions; private readonly _delay: number; private readonly _fetchImpl: typeof fetch; @@ -194,7 +192,7 @@ export class Client { } }; - return tryWith(this._opts.retries ?? 5, this._opts.timeout ?? 3000) + return tryWith(this._opts.retries ?? 5, timeout || this._opts.timeout || 3000) .then((value) => { finalCleanUp(); return value; diff --git a/src/context.ts b/src/context.ts index 6d043c4..ae52b3c 100644 --- a/src/context.ts +++ b/src/context.ts @@ -4,25 +4,28 @@ import { AudienceMatcher } from "./matcher"; import { insertUniqueSorted } from "./algorithm"; import { arrayEqualsShallow, isObject, isPromise } from "./utils"; import { SDK_VERSION } from "./version"; -import { ContextPublisher } from "./publisher"; -import { ContextDataProvider } from "./provider"; +import { ContextNotReadyError, ContextFinalizedError } from "./errors"; import type { Assignment, Attribute, ContextData, - ContextParams, Experiment, ExperimentData, Exposure, - Goal, + GoalAchievement, Units, PublishParams, + JSONValue, + CustomFieldValue, +} from "./models"; +import type { ClientRequestOptions, + ContextDataProvider, + ContextPublisher, EventLogger, EventName, - JSONValue, - CustomFieldValue, -} from "./types"; + ContextParams, +} from "./interfaces"; interface SDKLike { getClient(): { @@ -61,14 +64,17 @@ export class Context { private _data!: ContextData; private _exposures: Exposure[]; private _failed: boolean; + private _failedError: Error | null = null; private _finalized: boolean; - private _finalizing: boolean | Promise | null = null; - private _goals: Goal[]; + private _finalizing: Promise | null = null; + private _goals: GoalAchievement[]; private _index!: Record; private _indexVariables!: Record; private _overrides: Record; private _pending: number; private _attrsSeq: number; + private _attrsMapCache: Record | null = null; + private _attrsMapCacheSeq: number = -1; private _hashes?: Record; private _promise?: Promise; private _publishTimeout?: ReturnType; @@ -113,6 +119,7 @@ export class Context { this._init({}); this._failed = true; + this._failedError = error; delete this._promise; this._logError(error); @@ -139,13 +146,17 @@ export class Context { return this._failed; } - ready() { + readyError(): Error | null { + return this._failedError; + } + + ready(): Promise { if (this.isReady()) { return Promise.resolve(true); } return new Promise((resolve) => { - this._promise?.then(() => resolve(true)).catch((e) => resolve(e)); + this._promise?.then(() => resolve(true)).catch(() => resolve(true)); }); } @@ -170,6 +181,14 @@ export class Context { return this._dataProvider; } + getSDK() { + return this._sdk; + } + + getOptions(): ContextOptionsInternal { + return { ...this._opts }; + } + publish(requestOptions?: ClientRequestOptions) { this._checkReady(true); @@ -222,10 +241,14 @@ export class Context { } this._units[unitType] = uid; + + if (this._hashes) { + delete this._hashes[unitType]; + } } getUnits() { - return this._units; + return { ...this._units }; } units(units: Record) { @@ -243,6 +266,9 @@ export class Context { } attribute(attrName: string, value: unknown) { + if (typeof attrName !== "string" || attrName.trim().length === 0) { + throw new Error("Attribute name must be a non-empty string"); + } this._checkNotFinalized(); this._attrs.push({ name: attrName, value: value, setAt: Date.now() }); @@ -264,16 +290,25 @@ export class Context { } peek(experimentName: string) { + if (typeof experimentName !== "string" || experimentName.trim().length === 0) { + throw new Error("Experiment name must be a non-empty string"); + } this._checkReady(true); return this._peek(experimentName).variant; } treatment(experimentName: string) { + if (typeof experimentName !== "string" || experimentName.trim().length === 0) { + throw new Error("Experiment name must be a non-empty string"); + } this._checkReady(true); return this._treatment(experimentName).variant; } track(goalName: string, properties?: Record) { + if (typeof goalName !== "string" || goalName.trim().length === 0) { + throw new Error("Goal name must be a non-empty string"); + } this._checkNotFinalized(); return this._track(goalName, properties); } @@ -287,12 +322,18 @@ export class Context { return this._data.experiments?.map((x) => x.name); } - variableValue(key: string, defaultValue: string): string { + variableValue(key: string, defaultValue: T): T { + if (typeof key !== "string" || key.trim().length === 0) { + throw new Error("Variable key must be a non-empty string"); + } this._checkReady(true); return this._variableValue(key, defaultValue); } - peekVariableValue(key: string, defaultValue: string): string { + peekVariableValue(key: string, defaultValue: T): T { + if (typeof key !== "string" || key.trim().length === 0) { + throw new Error("Variable key must be a non-empty string"); + } this._checkReady(true); return this._peekVariable(key, defaultValue); } @@ -313,6 +354,13 @@ export class Context { } override(experimentName: string, variant: number) { + if (typeof experimentName !== "string" || experimentName.trim().length === 0) { + throw new Error("Experiment name must be a non-empty string"); + } + if (typeof variant !== "number" || variant < 0 || !Number.isInteger(variant)) { + throw new Error("Variant must be a non-negative integer"); + } + this._checkNotFinalized(); this._overrides = Object.assign(this._overrides, { [experimentName]: variant }); } @@ -323,6 +371,12 @@ export class Context { } customAssignment(experimentName: string, variant: number) { + if (typeof experimentName !== "string" || experimentName.trim().length === 0) { + throw new Error("Experiment name must be a non-empty string"); + } + if (typeof variant !== "number" || variant < 0 || !Number.isInteger(variant)) { + throw new Error("Variant must be a non-negative integer"); + } this._checkNotFinalized(); this._cassignments[experimentName] = variant; } @@ -339,26 +393,38 @@ export class Context { } customFieldValue(experimentName: string, key: string) { + if (typeof experimentName !== "string" || experimentName.trim().length === 0) { + throw new Error("Experiment name must be a non-empty string"); + } + if (typeof key !== "string" || key.trim().length === 0) { + throw new Error("Key must be a non-empty string"); + } this._checkReady(true); return this._customFieldValue(experimentName, key); } customFieldValueType(experimentName: string, key: string) { + if (typeof experimentName !== "string" || experimentName.trim().length === 0) { + throw new Error("Experiment name must be a non-empty string"); + } + if (typeof key !== "string" || key.trim().length === 0) { + throw new Error("Key must be a non-empty string"); + } this._checkReady(true); return this._customFieldValueType(experimentName, key); } private _checkNotFinalized() { if (this.isFinalized()) { - throw new Error("ABSmartly Context is finalized."); + throw new ContextFinalizedError(); } else if (this.isFinalizing()) { - throw new Error("ABSmartly Context is finalizing."); + throw new ContextFinalizedError(); } } private _checkReady(expectNotFinalized?: boolean) { if (!this.isReady()) { - throw new Error("ABSmartly Context is not yet ready."); + throw new ContextNotReadyError(); } if (expectNotFinalized) { @@ -367,13 +433,27 @@ export class Context { } private _getAttributesMap(): Record { + if (this._attrsMapCache !== null && this._attrsMapCacheSeq === this._attrsSeq) { + return this._attrsMapCache; + } const attrs: Record = {}; for (const attr of this._attrs) { attrs[attr.name] = attr.value; } + this._attrsMapCache = attrs; + this._attrsMapCacheSeq = this._attrsSeq; return attrs; } + private _evaluateAudience(audience: string): boolean | null { + try { + return this._audienceMatcher.evaluate(audience, this._getAttributesMap()); + } catch (error) { + this._logError(error as Error); + return null; + } + } + private _assign(experimentName: string): Assignment { const experimentMatches = (experiment: ExperimentData, assignment: Assignment) => { return ( @@ -387,9 +467,9 @@ export class Context { const audienceMatches = (experiment: ExperimentData, assignment: Assignment) => { if (experiment.audience && experiment.audience.length > 0) { - if (this._attrsSeq > (assignment.attrsSeq ?? 0)) { - const result = this._audienceMatcher.evaluate(experiment.audience, this._getAttributesMap()); - const newAudienceMismatch = typeof result === "boolean" ? !result : false; + if (this._attrsSeq > assignment.attrsSeq) { + const result = this._evaluateAudience(experiment.audience); + const newAudienceMismatch = typeof result === "boolean" ? !result : true; if (newAudienceMismatch !== assignment.audienceMismatch) { return false; @@ -435,6 +515,9 @@ export class Context { fullOn: false, custom: false, audienceMismatch: false, + trafficSplit: null, + variables: null, + attrsSeq: 0, }; this._assignments[experimentName] = assignment; @@ -452,11 +535,8 @@ export class Context { const unitType = experiment.data.unitType; if (experiment.data.audience && experiment.data.audience.length > 0) { - const result = this._audienceMatcher.evaluate(experiment.data.audience, this._getAttributesMap()); - - if (typeof result === "boolean") { - assignment.audienceMismatch = !result; - } + const result = this._evaluateAudience(experiment.data.audience); + assignment.audienceMismatch = typeof result === "boolean" ? !result : true; } if (experiment.data.audienceStrict && assignment.audienceMismatch) { @@ -514,7 +594,7 @@ export class Context { } if (experiment != null && assignment.variant < experiment.data.variants.length) { - assignment.variables = experiment.variables[assignment.variant]; + assignment.variables = experiment.variables[assignment.variant] ?? null; } return assignment; @@ -579,6 +659,8 @@ export class Context { if (experiment != null) { const field = experiment.data.customFieldValues?.find((x: CustomFieldValue) => x.name === key); if (field != null) { + if (field.value === null) return null; + switch (field.type) { case "text": case "string": @@ -590,15 +672,17 @@ export class Context { if (field.value === "null") return null; if (field.value === "") return ""; return JSON.parse(field.value); - } catch (_e) { - console.error(`Failed to parse JSON custom field value '${key}' for experiment '${experimentName}'`); + } catch (e) { + this._logError(new Error( + `Failed to parse JSON custom field value '${key}' for experiment '${experimentName}': ${(e as Error).message}` + )); return null; } case "boolean": return field.value === "true"; default: - console.error( - `Unknown custom field type '${field.type}' for experiment '${experimentName}' and key '${key}' - you may need to upgrade to the latest SDK version` + this._logError( + new Error(`Unknown custom field type '${field.type}' for experiment '${experimentName}' and key '${key}' - you may need to upgrade to the latest SDK version`) ); return null; } @@ -621,20 +705,20 @@ export class Context { return null; } - private _variableValue(key: string, defaultValue: string): string { + private _variableValue(key: string, defaultValue: T): T { const experiments = this._indexVariables[key]; if (experiments) { - for (const i in experiments) { - const experimentName = experiments[i]!.data.name; + for (const experiment of experiments) { + const experimentName = experiment.data.name; const assignment = this._assign(experimentName); - if (assignment.variables !== undefined) { + if (assignment.variables != null) { if (!assignment.exposed) { assignment.exposed = true; this._queueExposure(experimentName, assignment); } if (key in assignment.variables && (assignment.assigned || assignment.overridden)) { - return assignment.variables[key] as string; + return assignment.variables[key] as T; } } } @@ -643,15 +727,15 @@ export class Context { return defaultValue; } - private _peekVariable(key: string, defaultValue: string): string { + private _peekVariable(key: string, defaultValue: T): T { const experiments = this._indexVariables[key]; if (experiments) { - for (const i in experiments) { - const experimentName = experiments[i]!.data.name; + for (const experiment of experiments) { + const experimentName = experiment.data.name; const assignment = this._assign(experimentName); - if (assignment.variables !== undefined) { + if (assignment.variables != null) { if (key in assignment.variables && (assignment.assigned || assignment.overridden)) { - return assignment.variables[key] as string; + return assignment.variables[key] as T; } } } @@ -674,7 +758,7 @@ export class Context { private _track(goalName: string, properties?: Record) { const props = this._validateGoal(goalName, properties); - const goalEvent: Goal = { name: goalName, properties: props, achievedAt: Date.now() }; + const goalEvent: GoalAchievement = { name: goalName, properties: props, achievedAt: Date.now() }; this._logEvent("goal", goalEvent); this._goals.push(goalEvent); @@ -770,6 +854,17 @@ export class Context { request.attributes = allAttributes; } + // Snapshot and clear synchronously before async publish. + // New events accumulate in fresh arrays during in-flight publish. + // On failure, restore the snapshot so events are retried. + const pendingCount = this._pending; + const pendingExposures = this._exposures; + const pendingGoals = this._goals; + + this._pending = 0; + this._exposures = []; + this._goals = []; + this._publisher .publish(request, this._sdk, this, requestOptions) .then(() => { @@ -780,6 +875,10 @@ export class Context { } }) .catch((e: Error) => { + this._pending += pendingCount; + this._exposures.push(...pendingExposures); + this._goals.push(...pendingGoals); + this._logError(e); if (typeof callback === "function") { @@ -794,14 +893,18 @@ export class Context { } } } else { + this._logError(new Error( + `Discarding ${this._exposures.length} exposures and ${this._goals.length} goals because context failed to initialize` + )); + + this._pending = 0; + this._exposures = []; + this._goals = []; + if (typeof callback === "function") { callback(); } } - - this._pending = 0; - this._exposures = []; - this._goals = []; } } @@ -834,13 +937,21 @@ export class Context { private _logEvent(eventName: EventName, data?: unknown) { if (this._eventLogger) { - this._eventLogger(this, eventName, data as any); + try { + this._eventLogger(this, eventName, data as any); + } catch { + // ignore logger errors + } } } private _logError(error: Error) { if (this._eventLogger) { - this._eventLogger(this, "error", error); + try { + this._eventLogger(this, "error", error); + } catch { + // ignore logger errors + } } } @@ -864,7 +975,7 @@ export class Context { const index: Record = {}; const indexVariables: Record = {}; - (data.experiments || []).forEach((experiment) => { + for (const experiment of data.experiments || []) { const variables: Record[] = []; const entry = { data: experiment, @@ -873,9 +984,23 @@ export class Context { index[experiment.name] = entry; - experiment.variants.forEach((variant, i) => { + for (let i = 0; i < experiment.variants.length; i++) { + const variant = experiment.variants[i]!; const config = variant.config; - const parsed = config != null && config.length > 0 ? JSON.parse(config) : {}; + let parsed: Record = {}; + + if (config != null && config.length > 0) { + try { + const value = JSON.parse(config); + if (isObject(value)) { + parsed = value as Record; + } + } catch (error) { + this._logError(new Error( + `Failed to parse config for experiment '${experiment.name}' variant ${i}: ${(error as Error).message}` + )); + } + } for (const key of Object.keys(parsed)) { const value = entry; @@ -889,8 +1014,8 @@ export class Context { } variables[i] = parsed; - }); - }); + } + } this._index = index; this._indexVariables = indexVariables; diff --git a/src/errors.ts b/src/errors.ts index f921770..59f9371 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -1,3 +1,24 @@ +export class ABSmartlyError extends Error { + constructor(message: string) { + super(message); + this.name = "ABSmartlyError"; + } +} + +export class ContextNotReadyError extends ABSmartlyError { + constructor() { + super("Context is not yet ready"); + this.name = "ContextNotReadyError"; + } +} + +export class ContextFinalizedError extends ABSmartlyError { + constructor() { + super("Context has been finalized"); + this.name = "ContextFinalizedError"; + } +} + export class TimeoutError extends Error { readonly timeout: number; constructor(timeout: number) { diff --git a/src/hashing.ts b/src/hashing.ts index 0024d81..140c230 100644 --- a/src/hashing.ts +++ b/src/hashing.ts @@ -1,23 +1,27 @@ import { md5 } from "./md5"; export function stringToUint8Array(value: string): Uint8Array { - const n = value.length; - const array: number[] = []; - let k = 0; - for (let i = 0; i < n; ++i) { - const c = value.charCodeAt(i); + const utf8: number[] = []; + for (let i = 0; i < value.length; i++) { + let c = value.charCodeAt(i); + if (c >= 0xd800 && c <= 0xdbff && i + 1 < value.length) { + const next = value.charCodeAt(i + 1); + if (next >= 0xdc00 && next <= 0xdfff) { + c = ((c - 0xd800) << 10) + (next - 0xdc00) + 0x10000; + i++; + } + } if (c < 0x80) { - array[k++] = c; + utf8.push(c); } else if (c < 0x800) { - array[k++] = (c >> 6) | 192; - array[k++] = (c & 63) | 128; + utf8.push(0xc0 | (c >> 6), 0x80 | (c & 0x3f)); + } else if (c < 0x10000) { + utf8.push(0xe0 | (c >> 12), 0x80 | ((c >> 6) & 0x3f), 0x80 | (c & 0x3f)); } else { - array[k++] = (c >> 12) | 224; - array[k++] = ((c >> 6) & 63) | 128; - array[k++] = (c & 63) | 128; + utf8.push(0xf0 | (c >> 18), 0x80 | ((c >> 12) & 0x3f), 0x80 | ((c >> 6) & 0x3f), 0x80 | (c & 0x3f)); } } - return Uint8Array.from(array); + return new Uint8Array(utf8); } const BASE64_URL_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; diff --git a/src/index.ts b/src/index.ts index 97a226e..400730a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,29 +1,47 @@ export { SDK } from "./sdk"; export { Context } from "./context"; -export { ContextDataProvider } from "./provider"; -export { ContextPublisher } from "./publisher"; +export { DefaultContextDataProvider } from "./provider"; +export { DefaultContextPublisher } from "./publisher"; export { mergeConfig } from "./config"; +export { + ABSmartlyError, + ContextNotReadyError, + ContextFinalizedError, + TimeoutError, + RetryError, + AbortError, +} from "./errors"; + export type { ApplicationObject, - Attribute, Assignment, - ClientOptions, - ClientRequestOptions, + Attribute, ContextData, - ContextParams, CustomFieldValue, CustomFieldValueType, - EventLogger, - EventLoggerData, - EventName, Experiment, + ExperimentApplication, ExperimentData, + ExperimentVariant, Exposure, - Goal, + GoalAchievement, JSONValue, - NormalizedClientOptions, PublishParams, Unit, Units, -} from "./types"; +} from "./models"; + +export type { + Client, + ClientOptions, + ClientRequestOptions, + ContextDataProvider, + ContextOptions, + ContextParams, + ContextPublisher, + EventLogger, + EventLoggerData, + EventName, + SDKOptions, +} from "./interfaces"; diff --git a/src/interfaces.ts b/src/interfaces.ts new file mode 100644 index 0000000..ae5fe87 --- /dev/null +++ b/src/interfaces.ts @@ -0,0 +1,74 @@ +import type { + ApplicationObject, + ContextData, + Exposure, + GoalAchievement, + PublishParams, +} from "./models"; + +export type EventName = "error" | "ready" | "refresh" | "publish" | "exposure" | "goal" | "finalize"; + +export type EventLoggerData = Error | Exposure | GoalAchievement | ContextData | PublishParams; + +export type EventLogger = (context: unknown, eventName: EventName, data?: EventLoggerData) => void; + +export type ContextParams = { + units: Record; +}; + +export type ContextOptions = { + publishDelay: number; + refreshPeriod: number; + includeSystemAttributes?: boolean; +}; + +export interface ClientRequestOptions { + query?: Record; + path: string; + method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS"; + body?: Record; + auth?: boolean; + signal?: AbortSignal; + timeout?: number; +} + +export type ClientOptions = { + agent?: string; + apiKey: string; + application: string | ApplicationObject; + endpoint: string; + environment: string; + retries?: number; + timeout?: number; + keepalive?: boolean; + fetchImpl?: typeof fetch; + AbortControllerImpl?: typeof AbortController; +}; + +export type SDKOptions = { + client?: Client; + eventLogger?: EventLogger; + publisher?: ContextPublisher; + provider?: ContextDataProvider; +}; + +export interface Client { + getContext(options?: Partial): Promise; + publish(params: PublishParams, options?: ClientRequestOptions): Promise; + getAgent(): string; + getApplication(): ApplicationObject; + getEnvironment(): string; +} + +export interface ContextDataProvider { + getContextData(sdk: unknown, requestOptions?: Partial): Promise; +} + +export interface ContextPublisher { + publish( + request: PublishParams, + sdk: unknown, + context: unknown, + requestOptions?: ClientRequestOptions, + ): Promise; +} diff --git a/src/matcher.ts b/src/matcher.ts index d2c5687..3833493 100644 --- a/src/matcher.ts +++ b/src/matcher.ts @@ -5,15 +5,17 @@ export class AudienceMatcher { private readonly _jsonExpr = new JsonExpr(); evaluate(audienceString: string, vars: Record): boolean | null { + let audience; try { - const audience = JSON.parse(audienceString); - if (audience && audience.filter) { - if (Array.isArray(audience.filter) || isObject(audience.filter)) { - return this._jsonExpr.evaluateBooleanExpr(audience.filter, vars); - } - } + audience = JSON.parse(audienceString); } catch { - // invalid JSON + return null; + } + + if (audience && audience.filter) { + if (Array.isArray(audience.filter) || isObject(audience.filter)) { + return this._jsonExpr.evaluateBooleanExpr(audience.filter, vars); + } } return null; } diff --git a/src/types.ts b/src/models.ts similarity index 50% rename from src/types.ts rename to src/models.ts index 0639319..bd214d3 100644 --- a/src/types.ts +++ b/src/models.ts @@ -5,13 +5,22 @@ export type JSONValue = JSONPrimitive | JSONObject | JSONArray; export type CustomFieldValueType = "text" | "string" | "number" | "json" | "boolean"; -export type CustomFieldValue = { +export interface CustomFieldValue { name: string; - value: string; + value: string | null; type: CustomFieldValueType; -}; +} + +export interface ExperimentVariant { + name: string; + config: string | null; +} + +export interface ExperimentApplication { + name: string; +} -export type ExperimentData = { +export interface ExperimentData { id: number; name: string; unitType: string | null; @@ -25,20 +34,12 @@ export type ExperimentData = { split: number[]; seedHi: number; seedLo: number; - variants: { config: null | string }[]; - variables: Record; - variant: number; - overridden: boolean; - assigned: boolean; - exposed: boolean; - eligible: boolean; - fullOn: boolean; - custom: boolean; - audienceMismatch: boolean; + variants: ExperimentVariant[]; + applications: ExperimentApplication[]; customFieldValues: CustomFieldValue[] | null; -}; +} -export type Assignment = { +export interface Assignment { id: number; iteration: number; fullOnVariant: number; @@ -51,22 +52,22 @@ export type Assignment = { fullOn: boolean; custom: boolean; audienceMismatch: boolean; - trafficSplit?: number[]; - variables?: Record; - attrsSeq?: number; -}; + trafficSplit: number[] | null; + variables: Record | null; + attrsSeq: number; +} export type Experiment = { data: ExperimentData; variables: Record[]; }; -export type Unit = { +export interface Unit { type: string; uid: string | null; -}; +} -export type Exposure = { +export interface Exposure { id: number; name: string; exposedAt: number; @@ -78,71 +79,34 @@ export type Exposure = { fullOn: boolean; custom: boolean; audienceMismatch: boolean; -}; +} -export type Attribute = { +export interface Attribute { name: string; value: unknown; setAt: number; -}; +} export type Units = Record; -export type Goal = { +export interface GoalAchievement { name: string; properties: Record | null; achievedAt: number; -}; - -export type ContextParams = { - units: Record; -}; +} -export type ContextData = { +export interface ContextData { experiments?: ExperimentData[]; -}; +} export type ApplicationObject = { name: string; version: number | string }; -export type ClientRequestOptions = { - query?: Record; - path: string; - method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS"; - body?: Record; - auth?: boolean; - signal?: AbortSignal; - timeout?: number; -}; - -export type ClientOptions = { - agent?: string; - apiKey: string; - application: string | ApplicationObject; - endpoint: string; - environment: string; - retries?: number; - timeout?: number; - keepalive?: boolean; - fetchImpl?: typeof fetch; - AbortControllerImpl?: typeof AbortController; -}; - -export type NormalizedClientOptions = Omit, "application"> & { - application: ApplicationObject; -}; - -export type PublishParams = { +export interface PublishParams { units: Unit[]; publishedAt: number; hashed: boolean; sdkVersion: string; attributes?: Attribute[]; - goals?: Goal[]; + goals?: GoalAchievement[]; exposures?: Exposure[]; -}; - -export type EventName = "error" | "ready" | "refresh" | "publish" | "exposure" | "goal" | "finalize"; - -export type EventLoggerData = Error | Exposure | Goal | ContextData | PublishParams; - -export type EventLogger = (context: unknown, eventName: EventName, data?: EventLoggerData) => void; +} diff --git a/src/provider.ts b/src/provider.ts index 55c90cb..7375656 100644 --- a/src/provider.ts +++ b/src/provider.ts @@ -1,10 +1,11 @@ -import type { ClientRequestOptions, ContextData } from "./types"; +import type { ContextData } from "./models"; +import type { ClientRequestOptions, ContextDataProvider } from "./interfaces"; interface SDKLike { getClient(): { getContext(options?: Partial): Promise }; } -export class ContextDataProvider { +export class DefaultContextDataProvider implements ContextDataProvider { getContextData(sdk: SDKLike, requestOptions?: Partial): Promise { return sdk.getClient().getContext(requestOptions) as Promise; } diff --git a/src/publisher.ts b/src/publisher.ts index c5500c9..0c844e7 100644 --- a/src/publisher.ts +++ b/src/publisher.ts @@ -1,10 +1,11 @@ -import type { ClientRequestOptions, PublishParams } from "./types"; +import type { PublishParams } from "./models"; +import type { ClientRequestOptions, ContextPublisher } from "./interfaces"; interface SDKLike { getClient(): { publish(request: PublishParams, options?: ClientRequestOptions): Promise }; } -export class ContextPublisher { +export class DefaultContextPublisher implements ContextPublisher { publish(request: PublishParams, sdk: SDKLike, _context: unknown, requestOptions?: ClientRequestOptions): Promise { return sdk.getClient().publish(request, requestOptions); } diff --git a/src/sdk.ts b/src/sdk.ts index 7831e74..fcb60af 100644 --- a/src/sdk.ts +++ b/src/sdk.ts @@ -1,15 +1,19 @@ -import { Client } from "./client"; +import { DefaultClient } from "./client"; import { Context } from "./context"; -import { ContextPublisher } from "./publisher"; -import { ContextDataProvider } from "./provider"; +import { DefaultContextPublisher } from "./publisher"; +import { DefaultContextDataProvider } from "./provider"; import type { + Client, ClientOptions, ClientRequestOptions, - ContextData, + ContextDataProvider, ContextParams, + ContextPublisher, EventLogger, EventLoggerData, -} from "./types"; + SDKOptions, +} from "./interfaces"; +import type { ContextData } from "./models"; type ContextOptionsInput = { publisher?: ContextPublisher; @@ -23,13 +27,6 @@ type ContextOptionsInput = { type ContextOptionsFull = Required> & Omit; -export type SDKOptions = { - client?: Client; - eventLogger?: EventLogger; - publisher?: ContextPublisher; - provider?: ContextDataProvider; -}; - function isLongLivedApp(): boolean { return ( (typeof window !== "undefined" && typeof window.document !== "undefined") || @@ -61,20 +58,25 @@ export class SDK { private readonly _client: Client; constructor(options: ClientOptions & SDKOptions) { - const clientOptions = Object.assign( - { agent: "absmartly-javascript-sdk" }, - ...Object.entries(options || {}) - .filter((x) => CLIENT_OPTION_KEYS.indexOf(x[0]) !== -1) - .map((x) => ({ [x[0]]: x[1] })), - ) as ClientOptions; - - this._client = options.client || new Client(clientOptions); + if (options.client) { + this._client = options.client; + } else { + const clientOptions = Object.assign( + { agent: "absmartly-javascript-sdk" }, + ...Object.entries(options || {}) + .filter((x) => CLIENT_OPTION_KEYS.indexOf(x[0]) !== -1) + .map((x) => ({ [x[0]]: x[1] })), + ) as ClientOptions; + + this._client = new DefaultClient(clientOptions); + } + this._eventLogger = options.eventLogger || SDK.defaultEventLogger; - this._publisher = options.publisher || new ContextPublisher(); - this._provider = options.provider || new ContextDataProvider(); + this._publisher = options.publisher || new DefaultContextPublisher(); + this._provider = options.provider || new DefaultContextDataProvider(); } - getContextData(requestOptions: ClientRequestOptions): Promise { + getContextData(requestOptions?: Partial): Promise { return this._provider.getContextData(this, requestOptions); } diff --git a/src/utils.ts b/src/utils.ts index 485aea3..b059a8e 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -104,7 +104,7 @@ export function isEqualsDeep(a: unknown, b: unknown, astack?: unknown[], bstack? return false; } -export function arrayEqualsShallow(a?: unknown[], b?: unknown[]): boolean { +export function arrayEqualsShallow(a?: unknown[] | null, b?: unknown[] | null): boolean { return a === b || (a?.length === b?.length && !a?.some((va, vi) => b && va !== b[vi])); } From f6770b1bd7cc14ad74defbc5c7544b448b78fdf2 Mon Sep 17 00:00:00 2001 From: Jonas Alves Date: Sat, 18 Apr 2026 13:53:54 +0100 Subject: [PATCH 22/22] =?UTF-8?q?docs:=20update=20README=20for=20v2=20API?= =?UTF-8?q?=20=E2=80=94=20DI=20interfaces,=20error=20types,=20readyError,?= =?UTF-8?q?=20variable=20access?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 107 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 94 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index c9c5f6b..faad2de 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # A/B Smartly SDK [![npm version](https://badge.fury.io/js/%40absmartly%2Fjavascript-sdk.svg)](https://badge.fury.io/js/%40absmartly%2Fjavascript-sdk) -A/B Smartly - JavaScript SDK +A/B Smartly - JavaScript/TypeScript SDK ## Compatibility @@ -30,7 +30,7 @@ const sdk = new SDK({ ### Legacy Browsers A pre-built legacy bundle transpiled to ES2015 is available at `dist/index.legacy.js`. -**Important:** This is an ES2015 build, not an ES5 build. It can help with older browsers that support ES2015, but it does **not** restore IE11 or IE10 compatibility on its own. +**Important:** This is an ES2015 build, not an ES5 build. It does **not** support IE10/IE11. If you need legacy browser support, you must provide polyfills for missing APIs: @@ -132,6 +132,10 @@ const sdk = new SDK({ | `keepalive` | `boolean` | No | Enable keep-alive (default: `true`) | | `fetchImpl` | `typeof fetch` | No | Custom fetch implementation for legacy environments | | `AbortControllerImpl` | `typeof AbortController` | No | Custom AbortController for legacy environments | +| `client` | `Client` | No | Custom HTTP client implementing the `Client` interface | +| `eventLogger` | `EventLogger` | No | Custom event logger callback | +| `publisher` | `ContextPublisher` | No | Custom context publisher | +| `provider` | `ContextDataProvider` | No | Custom context data provider | #### Creating a new Context with promises ```typescript @@ -142,9 +146,11 @@ const context = sdk.createContext({ }); context.ready().then(() => { + if (context.isFailed()) { + console.error("Context failed to initialize:", context.readyError()); + // Context is still usable — all treatments return control (variant 0) + } console.log("ABSmartly Context ready!"); -}).catch((error) => { - console.log(error); }); ``` @@ -156,14 +162,16 @@ const context = sdk.createContext({ }, }); -try { - await context.ready(); - console.log("ABSmartly Context ready!"); -} catch (error) { - console.log(error); +await context.ready(); + +if (context.isFailed()) { + console.error("Context failed to initialize:", context.readyError()); + // Context is still usable — all treatments return control (variant 0) } ``` +> **Note:** `ready()` always resolves to `true`, even when initialization fails. A failed context is still "ready" — it simply has no experiment data, so all `treatment()` calls return `0` (control). Use `isFailed()` to check if initialization failed and `readyError()` to inspect the error. + #### Creating a new Context with pre-fetched data When doing full-stack experimentation with A/B Smartly, we recommend creating a context only once on the server-side. Creating a context involves a round-trip to the A/B Smartly event collector. @@ -237,6 +245,18 @@ if (context.treatment("exp_test_experiment") === 0) { } ``` +#### Accessing experiment variables +Experiment variables allow you to configure per-variant values directly from the A/B Smartly web console. + +```typescript +const buttonColor = context.variableValue("button.color", "grey"); // "grey" is the default +``` + +Use `peekVariableValue()` to access a variable without triggering an exposure: +```typescript +const buttonColor = context.peekVariableValue("button.color", "grey"); +``` + #### Tracking a goal achievement Goals are created in the A/B Smartly web console. ```typescript @@ -262,12 +282,12 @@ window.location = "https://www.absmartly.com"; #### Refreshing the context with fresh experiment data For long-running single-page-applications (SPA), the context is usually created once when the application is first reached. However, any experiments being tracked in your production code, but started after the context was created, will not be triggered. -To mitigate this, we can use the `refreshInterval` option when creating the context. +To mitigate this, we can use the `refreshPeriod` option when creating the context. ```typescript const context = sdk.createContext( { units: { session_id: "5ebf06d8cb5d8137290c4abb64155584fbdb64d8" } }, - { refreshInterval: 5 * 60 * 1000 }, + { refreshPeriod: 5 * 60 * 1000 }, ); ``` @@ -314,6 +334,7 @@ Currently, the SDK logs the following events: | `"goal"` | `Context.track()` method succeeds | goal data enqueued for publishing | | `"finalize"` | `Context.finalize()` method succeeds the first time | undefined | +> **Note:** The event logger is wrapped in a try/catch by the SDK. A broken logger will not crash SDK operations. #### Peek at treatment variants Although generally not recommended, it is sometimes necessary to peek at a treatment without triggering an exposure. @@ -338,6 +359,59 @@ context.overrides({ }); ``` +#### Custom fields +Experiments can have custom field values configured in the A/B Smartly web console. +```typescript +const keys = context.customFieldKeys(); +const value = context.customFieldValue("exp_test_experiment", "country"); +const type = context.customFieldValueType("exp_test_experiment", "country"); +``` + +#### Error handling +The SDK provides typed error classes for programmatic error handling: + +```typescript +import { + ABSmartlyError, + ContextNotReadyError, + ContextFinalizedError, + TimeoutError, + RetryError, +} from "@absmartly/javascript-sdk"; + +try { + context.treatment("exp_test"); +} catch (error) { + if (error instanceof ContextNotReadyError) { + // Context not ready yet — await context.ready() first + } else if (error instanceof ContextFinalizedError) { + // Context has been finalized — create a new one + } +} +``` + +#### Custom Client (Dependency Injection) +You can provide your own HTTP client implementation by implementing the `Client` interface: + +```typescript +import type { Client } from "@absmartly/javascript-sdk"; + +class MyCustomClient implements Client { + async getContext(options?) { /* ... */ } + async publish(params, options?) { /* ... */ } + getAgent() { return "my-custom-client"; } + getApplication() { return { name: "my-app", version: "1.0.0" }; } + getEnvironment() { return "production"; } +} + +const sdk = new SDK({ + client: new MyCustomClient(), + // No need for endpoint, apiKey, etc. when providing your own client +}); +``` + +Similarly, `ContextDataProvider` and `ContextPublisher` interfaces can be implemented for custom data fetching and publishing strategies. + #### HTTP request timeout It is possible to set a timeout per individual HTTP request, overriding the global timeout set for all request when instantiating the SDK object. @@ -345,7 +419,7 @@ Here is an example of setting a timeout only for the createContext request. ```typescript const context = sdk.createContext(request, { - refreshInterval: 5 * 60 * 1000, + refreshPeriod: 5 * 60 * 1000, }, { timeout: 1500, }); @@ -357,7 +431,7 @@ Sometimes it is useful to cancel an inflight HTTP request, for example, when the ```typescript const controller = new AbortController(); const context = sdk.createContext(request, { - refreshInterval: 5 * 60 * 1000, + refreshPeriod: 5 * 60 * 1000, }, { signal: controller.signal, }); @@ -374,6 +448,8 @@ clearTimeout(timeoutId); ### Breaking changes - **Named exports** instead of default export: `import { SDK } from "@absmartly/javascript-sdk"` instead of `import absmartly from "@absmartly/javascript-sdk"` +- **Provider/Publisher classes renamed**: `ContextDataProvider` → `DefaultContextDataProvider`, `ContextPublisher` → `DefaultContextPublisher`. The old names are now interfaces. +- **`Goal` type renamed** to `GoalAchievement` for clarity - **Node.js 14+** minimum (was Node.js 6+) - **IE10 and IE11 are not supported by the shipped bundles** - `index.legacy.js` is ES2015, not ES5. Supporting IE10/IE11 would require an additional ES5 build plus polyfills. - **No bundled polyfills** - `core-js`, `node-fetch`, and `rfdc` are no longer bundled. Legacy environments must provide polyfills explicitly. @@ -385,11 +461,16 @@ clearTimeout(timeoutId); - Optional polyfill injection (`fetchImpl`, `AbortControllerImpl`) - ESM, CJS, and IIFE builds from a single source - Smaller bundle size +- Interface-based dependency injection (`Client`, `ContextDataProvider`, `ContextPublisher`) +- Domain error classes (`ABSmartlyError`, `ContextNotReadyError`, `ContextFinalizedError`) +- `readyError()` method to inspect initialization failures +- Input validation on all public methods ### Removed exports - `AbortController` is no longer exported by the SDK package. - Use the platform/global `AbortController` instead. - In legacy environments, provide your own polyfill and pass it via `AbortControllerImpl`. +- `NormalizedClientOptions` is no longer exported (internal type). ## About A/B Smartly **A/B Smartly** is the leading provider of state-of-the-art, on-premises, full-stack experimentation platforms for engineering and product teams that want to confidently deploy features as fast as they can develop them.