From 5dc8432cb9248f31c4791673b2e5a0025af046ac Mon Sep 17 00:00:00 2001 From: Emmanuel Zamora Date: Tue, 9 Sep 2025 11:33:10 -0300 Subject: [PATCH 1/3] Add lint --- .github/workflows/test.yml | 3 + CHANGES.txt | 34 +- README.md | 54 +- eslint.config.mts | 30 + package-lock.json | 1381 +++++++++++++++++++- package.json | 16 +- src/__tests__/nodeSuites/client.spec.js | 65 +- src/__tests__/nodeSuites/provider.spec.js | 453 ++++--- src/__tests__/testUtils/eventSourceMock.js | 3 +- src/__tests__/testUtils/index.js | 6 +- src/lib/js-split-provider.ts | 41 +- 11 files changed, 1716 insertions(+), 370 deletions(-) create mode 100644 eslint.config.mts diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 14fff91..8a8fe06 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,5 +25,8 @@ jobs: - name: npm ci run: npm ci + - name: npm check + run: npm run check + - name: npm test run: npm run test diff --git a/CHANGES.txt b/CHANGES.txt index d4571ce..5f82f62 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,18 +1,28 @@ +1.2.0 (September 9, 2025) + - Up to date with @openfeature/server-sdk 1.19.0 + - Added tracking support + - Added “evaluate with details” support + 1.1.0 (June 16, 2025) -- Uses renamed @openfeature/js-sdk to @openfeature/server-sdk -- Up to date with spec 0.8.0 and @openfeature/server-sdk 1.18.0 -- Uses split sdk 11.4.0 + - Uses renamed @openfeature/js-sdk to @openfeature/server-sdk + - Up to date with spec 0.8.0 and @openfeature/server-sdk 1.18.0 + - Uses split sdk 11.4.0 + 1.0.4 -- Fixes issue with TS build -- Up to date with spec 0.5.0 and @openfeature/js-sdk 0.5.0 + - Fixes issue with TS build + - Up to date with spec 0.5.0 and @openfeature/js-sdk 0.5.0 + 1.0.3 -- Adds types definitions for TypeScript -- Up to date with spec 0.4.0 and @openfeature/js-sdk 0.4.0 + - Adds types definitions for TypeScript + - Up to date with spec 0.4.0 and @openfeature/js-sdk 0.4.0 + 1.0.2 -- Changes name from Node-specific implementation to generic JSON -- Up to date with spec 0.4.0 and @openfeature/js-sdk 0.4.0 + - Changes name from Node-specific implementation to generic JSON + - Up to date with spec 0.4.0 and @openfeature/js-sdk 0.4.0 + 1.0.1 -- Fixes issues with flag details and error codes in negative cases, adds unit tests -- Up to date with spec 0.4.0 and @openfeature/nodejs-sdk v0.3.2 + - Fixes issues with flag details and error codes in negative cases, adds unit tests + - Up to date with spec 0.4.0 and @openfeature/nodejs-sdk v0.3.2 + 1.0.0 -- First release. Up to date with spec 0.4.0, and @openfeature/nodejs-sdk v0.2.0 + - First release. Up to date with spec 0.4.0, and @openfeature/nodejs-sdk v0.2.0 diff --git a/README.md b/README.md index 6ee55d8..9f03a23 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ This Provider is designed to allow the use of OpenFeature with Split, the platform for controlled rollouts, serving features to your users via the Split feature flag to manage your complete customer experience. ## Compatibility +It supports **Node.js version 14.x or later**. ## Getting started @@ -62,6 +63,37 @@ OpenFeatureAPI.getInstance().setCtx(context) ```` If the context was set at the client or api level, it is not required to provide it during flag evaluation. +## Evaluate with details +Use the get*Details(...) APIs to get the value and rich context (variant, reason, error code, metadata). This provider includes the Split treatment config as a raw JSON string under flagMetadata["config"] + +```js +const booleanTreatment = await client.getBooleanDetails('boolFlag', false, context); + +const config = booleanTreatment.flagMetadata.config +``` + +## Tracking + +To use track(eventName, context, details) you must provide: + +- A non-blank `eventName`. +- A context with: + - `targetingKey` (non-blank). + - `trafficType` (string, e.g. "user" or "account"). + +Optional: + +- details with: + - `value`: numeric event value (defaults to 0). + - `properties`: map of attributes (prefer primitives: string/number/boolean/null). + +Example: +```js +const context = { targetingKey: 'user-123', trafficType: 'account' } +const details = { value: 19.99, plan: 'pro', coupon: 'WELCOME10' } + +client.track('checkout.completed', context, details) +``` ## Submitting issues The Split team monitors all issues submitted to this [issue tracker](https://github.com/splitio/split-openfeature-provider-nodejs/issues). We encourage you to use this issue tracker to submit any bug reports, feedback, and feature enhancements. We'll do our best to respond in a timely manner. @@ -80,16 +112,24 @@ To learn more about Split, contact hello@split.io, or get started with feature f Split has built and maintains SDKs for: -* Java [Github](https://github.com/splitio/java-client) [Docs](https://help.split.io/hc/en-us/articles/360020405151-Java-SDK) -* Javascript [Github](https://github.com/splitio/javascript-client) [Docs](https://help.split.io/hc/en-us/articles/360020448791-JavaScript-SDK) -* Node [Github](https://github.com/splitio/javascript-client) [Docs](https://help.split.io/hc/en-us/articles/360020564931-Node-js-SDK) * .NET [Github](https://github.com/splitio/dotnet-client) [Docs](https://help.split.io/hc/en-us/articles/360020240172--NET-SDK) -* Ruby [Github](https://github.com/splitio/ruby-client) [Docs](https://help.split.io/hc/en-us/articles/360020673251-Ruby-SDK) -* PHP [Github](https://github.com/splitio/php-client) [Docs](https://help.split.io/hc/en-us/articles/360020350372-PHP-SDK) -* Python [Github](https://github.com/splitio/python-client) [Docs](https://help.split.io/hc/en-us/articles/360020359652-Python-SDK) -* GO [Github](https://github.com/splitio/go-client) [Docs](https://help.split.io/hc/en-us/articles/360020093652-Go-SDK) * Android [Github](https://github.com/splitio/android-client) [Docs](https://help.split.io/hc/en-us/articles/360020343291-Android-SDK) +* Angular [Github](https://github.com/splitio/angular-sdk-plugin) [Docs](https://help.split.io/hc/en-us/articles/6495326064397-Angular-utilities) +* Elixir thin-client [Github](https://github.com/splitio/elixir-thin-client) [Docs](https://help.split.io/hc/en-us/articles/26988707417869-Elixir-Thin-Client-SDK) +* Flutter [Github](https://github.com/splitio/flutter-sdk-plugin) [Docs](https://help.split.io/hc/en-us/articles/8096158017165-Flutter-plugin) +* GO [Github](https://github.com/splitio/go-client) [Docs](https://help.split.io/hc/en-us/articles/360020093652-Go-SDK) * iOS [Github](https://github.com/splitio/ios-client) [Docs](https://help.split.io/hc/en-us/articles/360020401491-iOS-SDK) +* Java [Github](https://github.com/splitio/java-client) [Docs](https://help.split.io/hc/en-us/articles/360020405151-Java-SDK) +* JavaScript [Github](https://github.com/splitio/javascript-client) [Docs](https://help.split.io/hc/en-us/articles/360020448791-JavaScript-SDK) +* JavaScript for Browser [Github](https://github.com/splitio/javascript-browser-client) [Docs](https://help.split.io/hc/en-us/articles/360058730852-Browser-SDK) +* Node.js [Github](https://github.com/splitio/javascript-client) [Docs](https://help.split.io/hc/en-us/articles/360020564931-Node-js-SDK) +* PHP [Github](https://github.com/splitio/php-client) [Docs](https://help.split.io/hc/en-us/articles/360020350372-PHP-SDK) +* PHP thin-client [Github](https://github.com/splitio/php-thin-client) [Docs](https://help.split.io/hc/en-us/articles/18305128673933-PHP-Thin-Client-SDK) +* Python [Github](https://github.com/splitio/python-client) [Docs](https://help.split.io/hc/en-us/articles/360020359652-Python-SDK) +* React [Github](https://github.com/splitio/react-client) [Docs](https://help.split.io/hc/en-us/articles/360038825091-React-SDK) +* React Native [Github](https://github.com/splitio/react-native-client) [Docs](https://help.split.io/hc/en-us/articles/4406066357901-React-Native-SDK) +* Redux [Github](https://github.com/splitio/redux-client) [Docs](https://help.split.io/hc/en-us/articles/360038851551-Redux-SDK) +* Ruby [Github](https://github.com/splitio/ruby-client) [Docs](https://help.split.io/hc/en-us/articles/360020673251-Ruby-SDK) For a comprehensive list of open source projects visit our [Github page](https://github.com/splitio?utf8=%E2%9C%93&query=%20only%3Apublic%20). diff --git a/eslint.config.mts b/eslint.config.mts new file mode 100644 index 0000000..103904a --- /dev/null +++ b/eslint.config.mts @@ -0,0 +1,30 @@ +import js from "@eslint/js"; +import globals from "globals"; +import tseslint from "typescript-eslint"; +import { defineConfig } from "eslint/config"; +import jestPlugin from 'eslint-plugin-jest'; + + +export default defineConfig([ + { + files: ["**/*.{js,ts}"], + plugins: { js }, + extends: ["js/recommended"], + languageOptions: { + globals: globals.browser + } + }, + { + files: ['**/*.{test,spec}.{js,ts,jsx,tsx}', '**/__tests__/**/*.{js,ts,jsx,tsx}'], + plugins: { jest: jestPlugin }, + rules: { + ...jestPlugin.configs.recommended.rules, + }, + languageOptions: { + globals: { + ...globals.jest, + }, + }, + }, + tseslint.configs.recommended, +]); diff --git a/package-lock.json b/package-lock.json index 9860ca7..2985a6d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,26 +1,32 @@ { "name": "@splitsoftware/openfeature-js-split-provider", - "version": "1.1.0", + "version": "1.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@splitsoftware/openfeature-js-split-provider", - "version": "1.1.0", + "version": "1.2.0", "license": "Apache-2.0", "devDependencies": { + "@eslint/js": "^9.35.0", "@openfeature/server-sdk": "^1.19.0", "@splitsoftware/splitio": "^11.4.1", "@types/jest": "^30.0.0", "@types/node": "^24.3.1", "copyfiles": "^2.4.1", "cross-env": "^7.0.3", + "eslint": "^9.35.0", + "eslint-plugin-jest": "^28.14.0", + "globals": "^16.3.0", "jest": "^29.7.0", + "jiti": "^2.5.1", "replace": "^1.2.1", "rimraf": "^3.0.2", "ts-jest": "^29.4.1", "ts-node": "^10.5.0", - "typescript": "^4.9.5" + "typescript": "^4.9.5", + "typescript-eslint": "^8.43.0" }, "engines": { "node": ">=14" @@ -30,20 +36,6 @@ "@splitsoftware/splitio": "^11.4.1" } }, - "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/code-frame": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", @@ -60,9 +52,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz", - "integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz", + "integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==", "dev": true, "license": "MIT", "engines": { @@ -70,22 +62,22 @@ } }, "node_modules/@babel/core": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.3.tgz", - "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz", + "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", "dev": true, "license": "MIT", "dependencies": { - "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", - "@babel/helpers": "^7.28.3", - "@babel/parser": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.4", "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.3", - "@babel/types": "^7.28.2", + "@babel/traverse": "^7.28.4", + "@babel/types": "^7.28.4", + "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -217,27 +209,27 @@ } }, "node_modules/@babel/helpers": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.3.tgz", - "integrity": "sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", "dev": true, "license": "MIT", "dependencies": { "@babel/template": "^7.27.2", - "@babel/types": "^7.28.2" + "@babel/types": "^7.28.4" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz", - "integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", + "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.28.2" + "@babel/types": "^7.28.4" }, "bin": { "parser": "bin/babel-parser.js" @@ -501,18 +493,18 @@ } }, "node_modules/@babel/traverse": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.3.tgz", - "integrity": "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz", + "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==", "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.3", + "@babel/parser": "^7.28.4", "@babel/template": "^7.27.2", - "@babel/types": "^7.28.2", + "@babel/types": "^7.28.4", "debug": "^4.3.1" }, "engines": { @@ -520,9 +512,9 @@ } }, "node_modules/@babel/types": { - "version": "7.28.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", - "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", + "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", "dev": true, "license": "MIT", "dependencies": { @@ -564,6 +556,232 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", + "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", + "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@eslint/js": { + "version": "9.35.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.35.0.tgz", + "integrity": "sha512-30iXE9whjlILfWobBkNerJo+TXYsgVM5ERQwMcMKCHckHflCmf7wXDAHlARoWnh0s1U72WqlbeyE7iAcCzuCPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", + "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.15.2", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, "node_modules/@ioredis/commands": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.3.1.tgz", @@ -1441,6 +1659,17 @@ "@jridgewell/trace-mapping": "^0.3.24" } }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@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", @@ -1469,6 +1698,44 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/@openfeature/core": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@openfeature/core/-/core-1.9.0.tgz", @@ -1628,6 +1895,13 @@ "@babel/types": "^7.28.2" } }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/graceful-fs": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", @@ -1686,6 +1960,13 @@ "pretty-format": "^30.0.0" } }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "24.3.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.1.tgz", @@ -1727,6 +2008,277 @@ "dev": true, "license": "MIT" }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.43.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.43.0.tgz", + "integrity": "sha512-8tg+gt7ENL7KewsKMKDHXR1vm8tt9eMxjJBYINf6swonlWgkYn5NwyIgXpbbDxTNU5DgpDFfj95prcTq2clIQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.43.0", + "@typescript-eslint/type-utils": "8.43.0", + "@typescript-eslint/utils": "8.43.0", + "@typescript-eslint/visitor-keys": "8.43.0", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.43.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.43.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.43.0.tgz", + "integrity": "sha512-B7RIQiTsCBBmY+yW4+ILd6mF5h1FUwJsVvpqkrgpszYifetQ2Ke+Z4u6aZh0CblkUGIdR59iYVyXqqZGkZ3aBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.43.0", + "@typescript-eslint/types": "8.43.0", + "@typescript-eslint/typescript-estree": "8.43.0", + "@typescript-eslint/visitor-keys": "8.43.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.43.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.43.0.tgz", + "integrity": "sha512-htB/+D/BIGoNTQYffZw4uM4NzzuolCoaA/BusuSIcC8YjmBYQioew5VUZAYdAETPjeed0hqCaW7EHg+Robq8uw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.43.0", + "@typescript-eslint/types": "^8.43.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.43.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.43.0.tgz", + "integrity": "sha512-daSWlQ87ZhsjrbMLvpuuMAt3y4ba57AuvadcR7f3nl8eS3BjRc8L9VLxFLk92RL5xdXOg6IQ+qKjjqNEimGuAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.43.0", + "@typescript-eslint/visitor-keys": "8.43.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.43.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.43.0.tgz", + "integrity": "sha512-ALC2prjZcj2YqqL5X/bwWQmHA2em6/94GcbB/KKu5SX3EBDOsqztmmX1kMkvAJHzxk7TazKzJfFiEIagNV3qEA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.43.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.43.0.tgz", + "integrity": "sha512-qaH1uLBpBuBBuRf8c1mLJ6swOfzCXryhKND04Igr4pckzSEW9JX5Aw9AgW00kwfjWJF0kk0ps9ExKTfvXfw4Qg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.43.0", + "@typescript-eslint/typescript-estree": "8.43.0", + "@typescript-eslint/utils": "8.43.0", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.43.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.43.0.tgz", + "integrity": "sha512-vQ2FZaxJpydjSZJKiSW/LJsabFFvV7KgLC5DiLhkBcykhQj8iK9BOaDmQt74nnKdLvceM5xmhaTF+pLekrxEkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.43.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.43.0.tgz", + "integrity": "sha512-7Vv6zlAhPb+cvEpP06WXXy/ZByph9iL6BQRBDj4kmBsW98AqEeQHlj/13X+sZOrKSo9/rNKH4Ul4f6EICREFdw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.43.0", + "@typescript-eslint/tsconfig-utils": "8.43.0", + "@typescript-eslint/types": "8.43.0", + "@typescript-eslint/visitor-keys": "8.43.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.43.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.43.0.tgz", + "integrity": "sha512-S1/tEmkUeeswxd0GGcnwuVQPFWo8NzZTOMxCvw8BX7OMxnNae+i8Tm7REQen/SwUIPoPqfKn7EaZ+YLpiB3k9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.43.0", + "@typescript-eslint/types": "8.43.0", + "@typescript-eslint/typescript-estree": "8.43.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.43.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.43.0.tgz", + "integrity": "sha512-T+S1KqRD4sg/bHfLwrpF/K3gQLBM1n7Rp7OjjikjTEssI2YJzQpi5WXoynOaQ93ERIuq3O8RBTOUYDKszUCEHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.43.0", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -1740,6 +2292,16 @@ "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, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, "node_modules/acorn-walk": { "version": "8.3.4", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", @@ -1753,6 +2315,23 @@ "node": ">=0.4.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, + "license": "MIT", + "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/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -2087,9 +2666,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001739", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001739.tgz", - "integrity": "sha512-y+j60d6ulelrNSwpPyrHdl+9mJnQzHBr08xm48Qno0nSk4h3Qojh+ziv2qE6rXf4k3tadF4o1J/1tAbVm1NtnA==", + "version": "1.0.30001741", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001741.tgz", + "integrity": "sha512-QGUGitqsc8ARjLdgAfxETDhRbJ0REsP6O3I96TAth/mVjh2cYzN2u+3AzPP3aVSm2FehEItaJw1xd+IGBXWeSw==", "dev": true, "funding": [ { @@ -2389,6 +2968,13 @@ } } }, + "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, + "license": "MIT" + }, "node_modules/deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", @@ -2440,9 +3026,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.214", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.214.tgz", - "integrity": "sha512-TpvUNdha+X3ybfU78NoQatKvQEm1oq3lf2QbnmCEdw+Bd9RuIAY+hJTvq1avzHM0f7EJfnH3vbCnbzKzisc/9Q==", + "version": "1.5.215", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.215.tgz", + "integrity": "sha512-TIvGp57UpeNetj/wV/xpFNpWGb0b/ROw372lHPx5Aafx02gjTBtWnEEcaSX3W2dLM3OSdGGyHX/cHl01JQsLaQ==", "dev": true, "license": "ISC" }, @@ -2496,6 +3082,203 @@ "node": ">=8" } }, + "node_modules/eslint": { + "version": "9.35.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.35.0.tgz", + "integrity": "sha512-QePbBFMJFjgmlE+cXAlbHZbHpdFVS2E/6vzCy7aKlebddvl1vadiC4JFV5u/wqTkNUwEV8WrQi257jf5f06hrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.1", + "@eslint/core": "^0.15.2", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.35.0", + "@eslint/plugin-kit": "^0.3.5", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-jest": { + "version": "28.14.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-28.14.0.tgz", + "integrity": "sha512-P9s/qXSMTpRTerE2FQ0qJet2gKbcGyFTPAJipoKxmWqR6uuFqIqk8FuEfg5yBieOezVrEfAMZrEwJ6yEp+1MFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/utils": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "engines": { + "node": "^16.10.0 || ^18.12.0 || >=20.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^6.0.0 || ^7.0.0 || ^8.0.0", + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0", + "jest": "*" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + }, + "jest": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", @@ -2510,6 +3293,52 @@ "node": ">=4" } }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "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, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "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, + "license": "BSD-2-Clause", + "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, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -2548,17 +3377,54 @@ "resolved": "https://registry.npmjs.org/expect/-/expect-30.1.2.tgz", "integrity": "sha512-xvHszRavo28ejws8FpemjhwswGj4w/BetHIL8cU49u4sGyXDw2+p3YbeDbj6xzlxi6kWTjIRSTJ+9sNXPnF0Zg==", "dev": true, - "license": "MIT", + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "30.1.2", + "@jest/get-type": "30.1.0", + "jest-matcher-utils": "30.1.2", + "jest-message-util": "30.1.0", + "jest-mock": "30.0.5", + "jest-util": "30.0.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.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, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "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.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/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, + "license": "ISC", "dependencies": { - "@jest/expect-utils": "30.1.2", - "@jest/get-type": "30.1.0", - "jest-matcher-utils": "30.1.2", - "jest-message-util": "30.1.0", - "jest-mock": "30.0.5", - "jest-util": "30.0.5" + "is-glob": "^4.0.1" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">= 6" } }, "node_modules/fast-json-stable-stringify": { @@ -2568,6 +3434,23 @@ "dev": true, "license": "MIT" }, + "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, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "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", @@ -2578,6 +3461,19 @@ "bser": "2.1.1" } }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -2605,6 +3501,27 @@ "node": ">=8" } }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -2702,6 +3619,32 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.3.0.tgz", + "integrity": "sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -2709,6 +3652,13 @@ "dev": true, "license": "ISC" }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, "node_modules/handlebars": { "version": "4.7.8", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", @@ -2771,6 +3721,43 @@ "node": ">=10.17.0" } }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "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, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/import-local": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", @@ -2894,6 +3881,16 @@ "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, + "license": "MIT", + "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", @@ -2914,6 +3911,19 @@ "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, + "license": "MIT", + "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", @@ -4477,6 +5487,16 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/jiti": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz", + "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -4511,6 +5531,13 @@ "node": ">=6" } }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, "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", @@ -4518,6 +5545,20 @@ "dev": true, "license": "MIT" }, + "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, + "license": "MIT" + }, + "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, + "license": "MIT" + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -4531,6 +5572,16 @@ "node": ">=6" } }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, "node_modules/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -4551,6 +5602,20 @@ "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, + "license": "MIT", + "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", @@ -4606,6 +5671,13 @@ "dev": true, "license": "MIT" }, + "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, + "license": "MIT" + }, "node_modules/long": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", @@ -4676,6 +5748,16 @@ "dev": true, "license": "MIT" }, + "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, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -4786,9 +5868,9 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.20.tgz", + "integrity": "sha512-7gK6zSXEH6neM212JgfYFXe+GmZQM+fia5SsusuBIUgnPheLFBmIPhtFoAQRj8/7wASYQnbDlHPVwY0BefoFgA==", "dev": true, "license": "MIT" }, @@ -4852,6 +5934,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "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.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -4917,6 +6017,19 @@ "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, + "license": "MIT", + "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", @@ -5016,6 +6129,16 @@ "node": ">=8" } }, + "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, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/pretty-format": { "version": "30.0.5", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.5.tgz", @@ -5085,6 +6208,16 @@ "node": ">= 6" } }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/pure-rand": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", @@ -5102,6 +6235,27 @@ ], "license": "MIT" }, + "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" + } + ], + "license": "MIT" + }, "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -5440,6 +6594,17 @@ "node": ">=10" } }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -5457,6 +6622,30 @@ "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" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -5777,6 +6966,19 @@ "dev": true, "license": "MIT" }, + "node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, "node_modules/ts-jest": { "version": "29.4.1", "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.1.tgz", @@ -5907,6 +7109,19 @@ "dev": true, "license": "0BSD" }, + "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, + "license": "MIT", + "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", @@ -5944,6 +7159,30 @@ "node": ">=4.2.0" } }, + "node_modules/typescript-eslint": { + "version": "8.43.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.43.0.tgz", + "integrity": "sha512-FyRGJKUGvcFekRRcBKFBlAhnp4Ng8rhe8tuvvkR9OiU0gfd4vyvTRQHEckO6VDlH57jbeUQem2IpqPq9kLJH+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.43.0", + "@typescript-eslint/parser": "8.43.0", + "@typescript-eslint/typescript-estree": "8.43.0", + "@typescript-eslint/utils": "8.43.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, "node_modules/uglify-js": { "version": "3.19.3", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", @@ -6013,6 +7252,16 @@ "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, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -6093,6 +7342,16 @@ "dev": true, "license": "ISC" }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", diff --git a/package.json b/package.json index 6ca01cd..2470b09 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/openfeature-js-split-provider", - "version": "1.1.0", + "version": "1.2.0", "description": "Split OpenFeature Provider", "files": [ "README.md", @@ -39,18 +39,24 @@ "@splitsoftware/splitio": "^11.4.1" }, "devDependencies": { + "@eslint/js": "^9.35.0", "@openfeature/server-sdk": "^1.19.0", "@splitsoftware/splitio": "^11.4.1", "@types/jest": "^30.0.0", "@types/node": "^24.3.1", "copyfiles": "^2.4.1", "cross-env": "^7.0.3", + "eslint": "^9.35.0", + "eslint-plugin-jest": "^28.14.0", + "globals": "^16.3.0", "jest": "^29.7.0", + "jiti": "^2.5.1", "replace": "^1.2.1", "rimraf": "^3.0.2", "ts-jest": "^29.4.1", "ts-node": "^10.5.0", - "typescript": "^4.9.5" + "typescript": "^4.9.5", + "typescript-eslint": "^8.43.0" }, "scripts": { "build-esm": "rimraf es && tsc -outDir es", @@ -58,8 +64,10 @@ "build-cjs": "rimraf lib && tsc -outDir lib -m CommonJS", "postbuild-cjs": "cross-env NODE_ENV=cjs node scripts/copy.packages.json.js && ./scripts/build_cjs_replace_imports.sh", "build": "rimraf lib es && npm run build-cjs && npm run build-esm", + "check": "npm run check:lint", + "check:lint": "eslint src", "test": "cross-env NODE_ENV=test jest", - "publish:rc": "npm run test && npm run build && npm publish --tag rc", - "publish:stable": "npm run test && npm run build && npm publish" + "publish:rc": "npm run check && npm run test && npm run build && npm publish --tag rc", + "publish:stable": "npm run check && npm run test && npm run build && npm publish" } } diff --git a/src/__tests__/nodeSuites/client.spec.js b/src/__tests__/nodeSuites/client.spec.js index 97963e2..7bb0cc6 100644 --- a/src/__tests__/nodeSuites/client.spec.js +++ b/src/__tests__/nodeSuites/client.spec.js @@ -1,31 +1,32 @@ -import { OpenFeatureSplitProvider } from "../../lib/js-split-provider"; -import { getSplitClient } from "../testUtils"; +/* eslint-disable jest/no-conditional-expect */ +import { OpenFeatureSplitProvider } from '../../lib/js-split-provider'; +import { getSplitClient } from '../testUtils'; -import { OpenFeature } from "@openfeature/server-sdk"; +import { OpenFeature } from '@openfeature/server-sdk'; describe('client tests', () => { + let client; + let splitClient; + let provider; + beforeEach(() => { - splitClient = getSplitClient(); - provider = new OpenFeatureSplitProvider({ splitClient }); + splitClient = getSplitClient(); + provider = new OpenFeatureSplitProvider({ splitClient }); - OpenFeature.setProvider(provider); + OpenFeature.setProvider(provider); - client = OpenFeature.getClient('test'); - let evaluationContext = { - targetingKey: 'key' - }; - client.setContext(evaluationContext); + client = OpenFeature.getClient('test'); + let evaluationContext = { + targetingKey: 'key' + }; + client.setContext(evaluationContext); }); afterEach(() => { - splitClient.destroy(); - provider = undefined; + splitClient.destroy(); + provider = undefined; }); - let client; - let splitClient; - let provider; - test('use default test', async () => { let flagName = 'random-non-existent-feature'; @@ -107,7 +108,7 @@ describe('client tests', () => { expect(client.metadata.name).toBe('test'); }); - test('evaluate Boolean details test', async () => { + test('evaluate Boolean without details test', async () => { let details = await client.getBooleanDetails('some_other_feature', true); expect(details.flagKey).toBe('some_other_feature'); expect(details.reason).toBe('TARGETING_MATCH'); @@ -125,7 +126,7 @@ describe('client tests', () => { expect(details.errorCode).toBeUndefined(); }); - test('evaluate String details test', async () => { + test('evaluate String without details test', async () => { let details = await client.getStringDetails('some_other_feature', 'blah'); expect(details.flagKey).toBe('some_other_feature'); expect(details.reason).toBe('TARGETING_MATCH'); @@ -179,40 +180,40 @@ describe('client tests', () => { test('track: throws when missing eventName', async () => { try { - await client.track("", { targetingKey: "u1", trafficType: "user" }, {}); + await client.track('', { targetingKey: 'u1', trafficType: 'user' }, {}); } catch (e) { - expect(e.message).toBe("Missing eventName, required to track"); - expect(e.code).toBe("PARSE_ERROR"); + expect(e.message).toBe('Missing eventName, required to track'); + expect(e.code).toBe('PARSE_ERROR'); } }); test('track: throws when missing targetingKey', async () => { try { - await client.track("my-event", { trafficType: "user" }, {}); + await client.track('my-event', { trafficType: 'user' }, {}); } catch (e) { - expect(e.message).toBe("Missing targetingKey, required to track"); - expect(e.code).toBe("PARSE_ERROR"); + expect(e.message).toBe('Missing targetingKey, required to track'); + expect(e.code).toBe('PARSE_ERROR'); } }); test('track: throws when missing trafficType', async () => { try { - await client.track("my-event", { targetingKey: "u1" }, {}); + await client.track('my-event', { targetingKey: 'u1' }, {}); } catch (e) { - expect(e.message).toBe("Missing trafficType variable, required to track"); - expect(e.code).toBe("INVALID_CONTEXT"); + expect(e.message).toBe('Missing trafficType variable, required to track'); + expect(e.code).toBe('INVALID_CONTEXT'); } }); test('track: without value', async () => { const trackSpy = jest.spyOn(splitClient, 'track'); - await client.track("my-event", { targetingKey: "u1", trafficType: "user" }, { properties: { prop1: "value1" } }); - expect(trackSpy).toHaveBeenCalledWith("u1", "user", "my-event", undefined, { prop1: "value1" }); + await client.track('my-event', { targetingKey: 'u1', trafficType: 'user' }, { properties: { prop1: 'value1' } }); + expect(trackSpy).toHaveBeenCalledWith('u1', 'user', 'my-event', undefined, { prop1: 'value1' }); }); test('track: with value', async () => { const trackSpy = jest.spyOn(splitClient, 'track'); - await client.track("my-event", { targetingKey: "u1", trafficType: "user" }, { value: 9.99, properties: { prop1: "value1" } }); - expect(trackSpy).toHaveBeenCalledWith("u1", "user", "my-event", 9.99, { prop1: "value1" }); + await client.track('my-event', { targetingKey: 'u1', trafficType: 'user' }, { value: 9.99, properties: { prop1: 'value1' } }); + expect(trackSpy).toHaveBeenCalledWith('u1', 'user', 'my-event', 9.99, { prop1: 'value1' }); }); }); diff --git a/src/__tests__/nodeSuites/provider.spec.js b/src/__tests__/nodeSuites/provider.spec.js index 36cac70..a72875e 100644 --- a/src/__tests__/nodeSuites/provider.spec.js +++ b/src/__tests__/nodeSuites/provider.spec.js @@ -1,232 +1,229 @@ - -import { getSplitClient } from "../testUtils"; -import { OpenFeatureSplitProvider } from "../../lib/js-split-provider"; +/* eslint-disable jest/no-conditional-expect */ +import { getSplitClient } from '../testUtils'; +import { OpenFeatureSplitProvider } from '../../lib/js-split-provider'; describe('provider tests', () => { - beforeEach(() => { - splitClient = getSplitClient(); - provider = new OpenFeatureSplitProvider({ splitClient }); - }); - afterEach(() => { - splitClient.destroy(); - provider = undefined; - }); - - let splitClient; - let provider; - - test('evaluate Boolean null/empty test', async () => { - - try { - await provider.resolveBooleanEvaluation("", false, { targetingKey: "user1" }); - } catch (e) { - expect(e.message).toBe("flagKey must be a non-empty string"); - expect(e.code).toBe("FLAG_NOT_FOUND"); - } - }); - - test('evaluate Boolean control test', async () => { - try { - await provider.resolveBooleanEvaluation("non-existent-feature", false, { targetingKey: "user1" }); - } catch (e) { - expect(e.message).toBe("Received the 'control' value from Split."); - expect(e.code).toBe("FLAG_NOT_FOUND"); - } - }); - - test('evaluate Boolean true test', async () => { - const details = await provider.resolveBooleanEvaluation("my_feature", false, { targetingKey: "key" }); - expect(details.value).toBe(true); - expect(details.variant).toBe("on"); - expect(details.reason).toBe("TARGETING_MATCH"); - expect(details.flagMetadata).toEqual({ config: '{"desc" : "this applies only to ON treatment"}' }); - }); - - test('evaluate Boolean on test', async () => { - const details = await provider.resolveBooleanEvaluation("my_feature", true, { targetingKey: "key" }); - expect(details.value).toBe(true); - expect(details.variant).toBe("on"); - expect(details.reason).toBe("TARGETING_MATCH"); - expect(details.flagMetadata).toEqual({ config: '{\"desc\" : \"this applies only to ON treatment\"}' }); - }); - - test('evaluate Boolean false test', async () => { - const details = await provider.resolveBooleanEvaluation("some_other_feature", true, { targetingKey: "user1" }); - expect(details.value).toBe(false); - expect(details.variant).toBe("off"); - expect(details.reason).toBe("TARGETING_MATCH"); - expect(details.flagMetadata).toEqual({ config: '' }); - }); - - test('evaluate Boolean off test', async () => { - const details = await provider.resolveBooleanEvaluation("some_other_feature", false, { targetingKey: "user1" }); - expect(details.value).toBe(false); - expect(details.variant).toBe("off"); - expect(details.reason).toBe("TARGETING_MATCH"); - expect(details.flagMetadata).toEqual({ config: '' }); - }); - - test('evaluate Boolean error test', async () => { - try { - await provider.resolveBooleanEvaluation("int_feature", false, { targetingKey: "user1" }); - } catch (e) { - expect(e.message).toBe("Invalid boolean value for 32"); - expect(e.code).toBe("PARSE_ERROR"); - } - }); - - test('evaluate String null/empty test', async () => { - try { - await provider.resolveStringEvaluation("", "default", { targetingKey: "user1" }); - } catch (e) { - expect(e.message).toBe("flagKey must be a non-empty string"); - expect(e.code).toBe("FLAG_NOT_FOUND"); - } - }); - - test('evaluate String control test', async () => { - try { - await provider.resolveStringEvaluation("non-existent-feature", "default", { targetingKey: "user1" }); - } catch (e) { - expect(e.message).toBe("Received the 'control' value from Split."); - expect(e.code).toBe("FLAG_NOT_FOUND"); - } - }); - - test('evaluate String regular test', async () => { - try { - await provider.resolveStringEvaluation("string_feature", "default", { targetingKey: "user1" }); - } catch (e) { - expect(e.message).toBe("Received the 'control' value from Split."); - expect(e.code).toBe("FLAG_NOT_FOUND"); - } - }); - - test('evaluate String error test', async () => { - try { - await provider.resolveStringEvaluation("int_feature", "default", { targetingKey: "user1" }); - } catch (e) { - expect(e.message).toBe("Invalid string value for 32"); - expect(e.code).toBe("PARSE_ERROR"); - } - }); - - test('evaluate Number null/empty test', async () => { - try { - await provider.resolveNumberEvaluation("", 0, { targetingKey: "user1" }); - } catch (e) { - expect(e.message).toBe("flagKey must be a non-empty string"); - expect(e.code).toBe("FLAG_NOT_FOUND"); - } - }); - - test('evaluate Number control test', async () => { - try { - await provider.resolveNumberEvaluation("non-existent-feature", 0, { targetingKey: "user1" }); - } catch (e) { - expect(e.message).toBe("Received the 'control' value from Split."); - expect(e.code).toBe("FLAG_NOT_FOUND"); - } - }); - - test('evaluate Number regular test', async () => { - const details = await provider.resolveNumberEvaluation("int_feature", 0, { targetingKey: "user1" }); - expect(details.value).toBe(32); - expect(details.variant).toBe("32"); - expect(details.reason).toBe("TARGETING_MATCH"); - expect(details.flagMetadata).toEqual({ config: '{\"desc\" : \"this applies only to number treatment\"}' }); - }); - - test('evaluate Number error test', async () => { - try { - await provider.resolveNumberEvaluation("my_feature", 0, { targetingKey: "user1" }); - } catch (e) { - expect(e.message).toBe("Invalid numeric value off"); - expect(e.code).toBe("PARSE_ERROR"); - } - }); - - test('evaluate Structure null/empty test', async () => { - try { - await provider.resolveObjectEvaluation("", {}, { targetingKey: "user1" }); - } catch (e) { - expect(e.message).toBe("flagKey must be a non-empty string"); - expect(e.code).toBe("FLAG_NOT_FOUND"); - } - }); - - test('evaluate Structure control test', async () => { - try { - await provider.resolveObjectEvaluation("non-existent-feature", {}, { targetingKey: "user1" }); - } catch (e) { - expect(e.message).toBe("Received the 'control' value from Split."); - expect(e.code).toBe("FLAG_NOT_FOUND"); - } - }); - - test('evaluate Structure regular test', async () => { - const details = await provider.resolveObjectEvaluation("obj_feature", {}, { targetingKey: "user1" }); - expect(details.value).toEqual({ key: "value" }); - expect(details.variant).toBe('{"key": "value"}'); - expect(details.reason).toBe("TARGETING_MATCH"); - expect(details.flagMetadata).toEqual({ config: '{\"desc\" : \"this applies only to obj treatment\"}' }); - }); - - test('evaluate Structure error test', async () => { - try { - await provider.resolveObjectEvaluation("int_feature", {}, { targetingKey: "user1" }); - } catch (e) { - expect(e.message).toBe("Error parsing 32 as JSON, ParseError: Flag value 32 had unexpected type number, expected \"object\""); - expect(e.code).toBe("PARSE_ERROR"); - } - }); - - test('track: throws when missing eventName', async () => { - try { - await provider.track("", { targetingKey: "u1", trafficType: "user" }, {}); - } catch (e) { - expect(e.message).toBe("Missing eventName, required to track"); - expect(e.code).toBe("PARSE_ERROR"); - } - }); - - test('track: throws when missing trafficType', async () => { - try { - await provider.track("evt", { targetingKey: "u1" }, {}); - } catch (e) { - expect(e.message).toBe("Missing trafficType variable, required to track"); - expect(e.code).toBe("INVALID_CONTEXT"); - } - }); - - test('track: throws when missing targetingKey', async () => { - try { - await provider.track("evt", { trafficType: "user" }, {}); - } catch (e) { - expect(e.message).toBe("Missing targetingKey, required to track"); - expect(e.code).toBe("TARGETING_KEY_MISSING"); - } - }); - - - test('track: ok without details', async () => { - const trackSpy = jest.spyOn(splitClient, 'track'); - await provider.track("view", { targetingKey: "u1", trafficType: "user" }, null); - expect(trackSpy).toHaveBeenCalledTimes(1); - expect(trackSpy).toHaveBeenCalledWith("u1", "user", "view", undefined, {}); - }); - - test('track: ok with details', async () => { - const trackSpy = jest.spyOn(splitClient, 'track'); - await provider.track( - "purchase", - { targetingKey: "u1", trafficType: "user" }, - { value: 9.99, properties: { plan: "pro", beta: true } } - ); - expect(trackSpy).toHaveBeenCalledTimes(1); - expect(trackSpy).toHaveBeenCalledWith("u1", "user", "purchase", 9.99, { plan: "pro", beta: true }); - }); + let splitClient; + let provider; + + beforeEach(() => { + splitClient = getSplitClient(); + provider = new OpenFeatureSplitProvider({ splitClient }); + }); + + afterEach(() => { + splitClient.destroy(); + provider = undefined; + }); + + test('evaluate Boolean null/empty test', async () => { + try { + await provider.resolveBooleanEvaluation('', false, { targetingKey: 'user1' }); + } catch (e) { + expect(e.message).toBe('flagKey must be a non-empty string'); + expect(e.code).toBe('FLAG_NOT_FOUND'); + } + }); + + test('evaluate Boolean control test', async () => { + try { + await provider.resolveBooleanEvaluation('non-existent-feature', false, { targetingKey: 'user1' }); + } catch (e) { + expect(e.message).toBe('Received the "control" value from Split.'); + expect(e.code).toBe('FLAG_NOT_FOUND'); + } + }); + + test('evaluate Boolean true test', async () => { + const details = await provider.resolveBooleanEvaluation('my_feature', false, { targetingKey: 'key' }); + expect(details.value).toBe(true); + expect(details.variant).toBe('on'); + expect(details.reason).toBe('TARGETING_MATCH'); + expect(details.flagMetadata).toEqual({ config: '{"desc" : "this applies only to ON treatment"}' }); + }); + + test('evaluate Boolean on test', async () => { + const details = await provider.resolveBooleanEvaluation('my_feature', true, { targetingKey: 'key' }); + expect(details.value).toBe(true); + expect(details.variant).toBe('on'); + expect(details.reason).toBe('TARGETING_MATCH'); + expect(details.flagMetadata).toEqual({ config: '{"desc" : "this applies only to ON treatment"}' }); + }); + + test('evaluate Boolean false test', async () => { + const details = await provider.resolveBooleanEvaluation('some_other_feature', true, { targetingKey: 'user1' }); + expect(details.value).toBe(false); + expect(details.variant).toBe('off'); + expect(details.reason).toBe('TARGETING_MATCH'); + expect(details.flagMetadata).toEqual({ config: '' }); + }); + + test('evaluate Boolean off test', async () => { + const details = await provider.resolveBooleanEvaluation('some_other_feature', false, { targetingKey: 'user1' }); + expect(details.value).toBe(false); + expect(details.variant).toBe('off'); + expect(details.reason).toBe('TARGETING_MATCH'); + expect(details.flagMetadata).toEqual({ config: '' }); + }); + + test('evaluate Boolean error test', async () => { + try { + await provider.resolveBooleanEvaluation('int_feature', false, { targetingKey: 'user1' }); + } catch (e) { + expect(e.message).toBe('Invalid boolean value for 32'); + expect(e.code).toBe('PARSE_ERROR'); + } + }); + + test('evaluate String null/empty test', async () => { + try { + await provider.resolveStringEvaluation('', 'default', { targetingKey: 'user1' }); + } catch (e) { + expect(e.message).toBe('flagKey must be a non-empty string'); + expect(e.code).toBe('FLAG_NOT_FOUND'); + } + }); + + test('evaluate String control test', async () => { + try { + await provider.resolveStringEvaluation('non-existent-feature', 'default', { targetingKey: 'user1' }); + } catch (e) { + expect(e.message).toBe('Received the "control" value from Split.'); + expect(e.code).toBe('FLAG_NOT_FOUND'); + } + }); + + test('evaluate String regular test', async () => { + try { + await provider.resolveStringEvaluation('string_feature', 'default', { targetingKey: 'user1' }); + } catch (e) { + expect(e.message).toBe('Received the "control" value from Split.'); + expect(e.code).toBe('FLAG_NOT_FOUND'); + } + }); + + test('evaluate String error test', async () => { + try { + await provider.resolveStringEvaluation('int_feature', 'default', { targetingKey: 'user1' }); + } catch (e) { + expect(e.message).toBe('Invalid string value for 32'); + expect(e.code).toBe('PARSE_ERROR'); + } + }); + + test('evaluate Number null/empty test', async () => { + try { + await provider.resolveNumberEvaluation('', 0, { targetingKey: 'user1' }); + } catch (e) { + expect(e.message).toBe('flagKey must be a non-empty string'); + expect(e.code).toBe('FLAG_NOT_FOUND'); + } + }); + + test('evaluate Number control test', async () => { + try { + await provider.resolveNumberEvaluation('non-existent-feature', 0, { targetingKey: 'user1' }); + } catch (e) { + expect(e.message).toBe('Received the "control" value from Split.'); + expect(e.code).toBe('FLAG_NOT_FOUND'); + } + }); + + test('evaluate Number regular test', async () => { + const details = await provider.resolveNumberEvaluation('int_feature', 0, { targetingKey: 'user1' }); + expect(details.value).toBe(32); + expect(details.variant).toBe('32'); + expect(details.reason).toBe('TARGETING_MATCH'); + expect(details.flagMetadata).toEqual({ config: '{"desc" : "this applies only to number treatment"}' }); + }); + + test('evaluate Number error test', async () => { + try { + await provider.resolveNumberEvaluation('my_feature', 0, { targetingKey: 'user1' }); + } catch (e) { + expect(e.message).toBe('Invalid numeric value off'); + expect(e.code).toBe('PARSE_ERROR'); + } + }); + + test('evaluate Structure null/empty test', async () => { + try { + await provider.resolveObjectEvaluation('', {}, { targetingKey: 'user1' }); + } catch (e) { + expect(e.message).toBe('flagKey must be a non-empty string'); + expect(e.code).toBe('FLAG_NOT_FOUND'); + } + }); + + test('evaluate Structure control test', async () => { + try { + await provider.resolveObjectEvaluation('non-existent-feature', {}, { targetingKey: 'user1' }); + } catch (e) { + expect(e.message).toBe('Received the "control" value from Split.'); + expect(e.code).toBe('FLAG_NOT_FOUND'); + } + }); + + test('evaluate Structure regular test', async () => { + const details = await provider.resolveObjectEvaluation('obj_feature', {}, { targetingKey: 'user1' }); + expect(details.value).toEqual({ key: 'value' }); + expect(details.variant).toBe('{"key": "value"}'); + expect(details.reason).toBe('TARGETING_MATCH'); + expect(details.flagMetadata).toEqual({ config: '{"desc" : "this applies only to obj treatment"}' }); + }); + + test('evaluate Structure error test', async () => { + try { + await provider.resolveObjectEvaluation('int_feature', {}, { targetingKey: 'user1' }); + } catch (e) { + expect(e.message).toBe('Error parsing 32 as JSON, ParseError: Flag value 32 had unexpected type number, expected "object"'); + expect(e.code).toBe('PARSE_ERROR'); + } + }); + + test('track: throws when missing eventName', async () => { + try { + await provider.track('', { targetingKey: 'u1', trafficType: 'user' }, {}); + } catch (e) { + expect(e.message).toBe('Missing eventName, required to track'); + expect(e.code).toBe('PARSE_ERROR'); + } + }); + + test('track: throws when missing trafficType', async () => { + try { + await provider.track('evt', { targetingKey: 'u1' }, {}); + } catch (e) { + expect(e.message).toBe('Missing trafficType variable, required to track'); + expect(e.code).toBe('INVALID_CONTEXT'); + } + }); + + test('track: throws when missing targetingKey', async () => { + try { + await provider.track('evt', { trafficType: 'user' }, {}); + } catch (e) { + expect(e.message).toBe('Missing targetingKey, required to track'); + expect(e.code).toBe('TARGETING_KEY_MISSING'); + } + }); + + test('track: ok without details', async () => { + const trackSpy = jest.spyOn(splitClient, 'track'); + await provider.track('view', { targetingKey: 'u1', trafficType: 'user' }, null); + expect(trackSpy).toHaveBeenCalledTimes(1); + expect(trackSpy).toHaveBeenCalledWith('u1', 'user', 'view', undefined, {}); + }); + + test('track: ok with details', async () => { + const trackSpy = jest.spyOn(splitClient, 'track'); + await provider.track( + 'purchase', + { targetingKey: 'u1', trafficType: 'user' }, + { value: 9.99, properties: { plan: 'pro', beta: true } } + ); + expect(trackSpy).toHaveBeenCalledTimes(1); + expect(trackSpy).toHaveBeenCalledWith('u1', 'user', 'purchase', 9.99, { plan: 'pro', beta: true }); + }); }); -import { OpenFeatureSplitProvider } from "../../lib/js-split-provider"; - \ No newline at end of file diff --git a/src/__tests__/testUtils/eventSourceMock.js b/src/__tests__/testUtils/eventSourceMock.js index 085db03..7740954 100644 --- a/src/__tests__/testUtils/eventSourceMock.js +++ b/src/__tests__/testUtils/eventSourceMock.js @@ -32,7 +32,6 @@ export default class EventSource { this.url = url; this.withCredentials = eventSourceInitDict.withCredentials; this.readyState = 0; - // eslint-disable-next-line no-undef this.__emitter = new EventEmitter(); this.__eventSourceInitDict = arguments[1]; sources[url] = this; @@ -82,4 +81,4 @@ export default class EventSource { EventSource.CONNECTING = 0; EventSource.OPEN = 1; -EventSource.CLOSED = 2; \ No newline at end of file +EventSource.CLOSED = 2; diff --git a/src/__tests__/testUtils/index.js b/src/__tests__/testUtils/index.js index 935f2f3..0d86c6a 100644 --- a/src/__tests__/testUtils/index.js +++ b/src/__tests__/testUtils/index.js @@ -1,6 +1,4 @@ -import { OpenFeatureSplitProvider } from "../.."; -import { OpenFeature } from "@openfeature/server-sdk"; -import { SplitFactory } from "@splitsoftware/splitio"; +import { SplitFactory } from '@splitsoftware/splitio'; const DEFAULT_ERROR_MARGIN = 50; // 0.05 secs @@ -84,4 +82,4 @@ export function getSplitClient(apiKey = 'localhost') { features: './split.yaml', debug: 'DEBUG' }).client(); -} \ No newline at end of file +} diff --git a/src/lib/js-split-provider.ts b/src/lib/js-split-provider.ts index 5a2c5ea..82e71db 100644 --- a/src/lib/js-split-provider.ts +++ b/src/lib/js-split-provider.ts @@ -9,8 +9,8 @@ import { StandardResolutionReasons, TrackingEventDetails, InvalidContextError, -} from "@openfeature/server-sdk"; -import type SplitIO from "@splitsoftware/splitio/types/splitio"; +} from '@openfeature/server-sdk'; +import type SplitIO from '@splitsoftware/splitio/types/splitio'; export interface SplitProviderOptions { splitClient: SplitIO.IClient; @@ -21,12 +21,12 @@ type Consumer = { attributes: SplitIO.Attributes; }; -const CONTROL_VALUE_ERROR_MESSAGE = "Received the 'control' value from Split."; -const CONTROL_TREATMENT = "control"; +const CONTROL_VALUE_ERROR_MESSAGE = 'Received the "control" value from Split.'; +const CONTROL_TREATMENT = 'control'; export class OpenFeatureSplitProvider implements Provider { metadata = { - name: "split", + name: 'split', }; private initialized: Promise; private client: SplitIO.IClient; @@ -34,6 +34,7 @@ export class OpenFeatureSplitProvider implements Provider { constructor(options: SplitProviderOptions) { this.client = options.splitClient; this.initialized = new Promise((resolve) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any if ((this.client as any).__getStatus().isReady) { console.log(`${this.metadata.name} provider initialized`); resolve(); @@ -57,11 +58,11 @@ export class OpenFeatureSplitProvider implements Provider { ); const treatment = details.value.toLowerCase(); - if ( treatment === "on" || treatment === "true" ) { + if ( treatment === 'on' || treatment === 'true' ) { return { ...details, value: true }; } - if ( treatment === "off" || treatment === "false" ) { + if ( treatment === 'off' || treatment === 'false' ) { return { ...details, value: false }; } @@ -110,12 +111,12 @@ export class OpenFeatureSplitProvider implements Provider { ): Promise> { if (!consumer.key) { throw new TargetingKeyMissingError( - "The Split provider requires a targeting key." + 'The Split provider requires a targeting key.' ); } - if (flagKey == null || flagKey === "") { + if (flagKey == null || flagKey === '') { throw new FlagNotFoundError( - "flagKey must be a non-empty string" + 'flagKey must be a non-empty string' ); } @@ -146,21 +147,21 @@ export class OpenFeatureSplitProvider implements Provider { // targetingKey is always required const { targetingKey } = context; - if (targetingKey == null || targetingKey === "") - throw new TargetingKeyMissingError("Missing targetingKey, required to track"); + if (targetingKey == null || targetingKey === '') + throw new TargetingKeyMissingError('Missing targetingKey, required to track'); // eventName is always required - if (trackingEventName == null || trackingEventName === "") - throw new ParseError("Missing eventName, required to track"); + if (trackingEventName == null || trackingEventName === '') + throw new ParseError('Missing eventName, required to track'); // trafficType is always required - const ttVal = context["trafficType"]; + const ttVal = context['trafficType']; const trafficType = - ttVal != null && typeof ttVal === "string" && ttVal.trim() !== "" + ttVal != null && typeof ttVal === 'string' && ttVal.trim() !== '' ? ttVal : null; - if (trafficType == null || trafficType === "") - throw new InvalidContextError("Missing trafficType variable, required to track"); + if (trafficType == null || trafficType === '') + throw new InvalidContextError('Missing trafficType variable, required to track'); let value; let properties: SplitIO.Properties = {}; @@ -176,7 +177,7 @@ export class OpenFeatureSplitProvider implements Provider { this.client.track(targetingKey, trafficType, trackingEventName, value, properties); } - //Transform the context into an object useful for the Split API, an key string with arbitrary Split "Attributes". + //Transform the context into an object useful for the Split API, an key string with arbitrary Split 'Attributes'. private transformContext(context: EvaluationContext): Consumer { const { targetingKey, ...attributes } = context; return { @@ -206,7 +207,7 @@ export class OpenFeatureSplitProvider implements Provider { // we may want to allow the parsing to be customized. try { const value = JSON.parse(stringValue); - if (typeof value !== "object") { + if (typeof value !== 'object') { throw new ParseError( `Flag value ${stringValue} had unexpected type ${typeof value}, expected "object"` ); From 9a35b811e7d4e002b311b9dcf9941553771ac49c Mon Sep 17 00:00:00 2001 From: ZamoraEmmanuel <87494075+ZamoraEmmanuel@users.noreply.github.com> Date: Wed, 10 Sep 2025 17:10:56 -0300 Subject: [PATCH 2/3] Update README.md Co-authored-by: Emiliano Sanchez --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 9f03a23..2fb64ba 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,6 @@ const context: EvaluationContext = { targetingKey: 'TARGETING_KEY', }; OpenFeatureAPI.getInstance().setCtx(context) -```` If the context was set at the client or api level, it is not required to provide it during flag evaluation. ## Evaluate with details From e344782e3b10df760f46fb03db6c929660dfc57d Mon Sep 17 00:00:00 2001 From: Emmanuel Zamora Date: Wed, 10 Sep 2025 17:12:34 -0300 Subject: [PATCH 3/3] fix typo --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 2fb64ba..9bcf14f 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,7 @@ const context: EvaluationContext = { targetingKey: 'TARGETING_KEY', }; OpenFeatureAPI.getInstance().setCtx(context) +``` If the context was set at the client or api level, it is not required to provide it during flag evaluation. ## Evaluate with details